Repository: mcpcpc/kirc
Branch: main
Commit: 5f3b746ad57d
Files: 37
Total size: 163.3 KB
Directory structure:
gitextract_cdfbzy5t/
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── config.mk
├── include/
│ ├── ansi.h
│ ├── base64.h
│ ├── config.h
│ ├── ctcp.h
│ ├── dcc.h
│ ├── editor.h
│ ├── event.h
│ ├── handler.h
│ ├── helper.h
│ ├── kirc.h
│ ├── network.h
│ ├── output.h
│ ├── protocol.h
│ ├── terminal.h
│ ├── transport.h
│ └── utf8.h
├── kirc.1
└── src/
├── base64.c
├── config.c
├── ctcp.c
├── dcc.c
├── editor.c
├── event.c
├── handler.c
├── helper.c
├── main.c
├── network.c
├── output.c
├── protocol.c
├── terminal.c
├── transport.c
└── utf8.c
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
kirc
*.a
*.o
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021-2025 Michael Czigler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
.POSIX:
.SUFFIXES:
include config.mk
CFLAGS += -std=c99 -pedantic -Wall -Wextra
CFLAGS += -Wformat-security -Wwrite-strings
CFLAGS += -Wno-unused-parameter
CFLAGS += -g -Iinclude -Isrc
BIN = kirc
SRC = src
BUILD = build
# Discover all source files
SRCS = $(wildcard $(SRC)/*.c)
# Create matching build/*.o paths
OBJS = $(SRCS:$(SRC)/%.c=$(BUILD)/%.o)
all: $(BIN)
$(BIN): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(BUILD):
mkdir -p $@
# Pattern rule: build/xyz.o ← src/xyz.c
$(BUILD)/%.o: $(SRC)/%.c | $(BUILD)
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(BIN) $(BUILD)/*
install:
mkdir -p $(DESTDIR)$(BINDIR)
cp -f $(BIN) $(DESTDIR)$(BINDIR)
chmod 755 $(DESTDIR)$(BINDIR)/$(BIN)
mkdir -p $(DESTDIR)$(MANDIR)/man1
cp -f $(BIN).1 $(DESTDIR)$(MANDIR)/man1
chmod 644 $(DESTDIR)$(MANDIR)/man1/$(BIN).1
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(BIN)
rm -f $(DESTDIR)$(MANDIR)/man1/$(BIN).1
.PHONY: all clean install uninstall
================================================
FILE: README.md
================================================
kirc
====
kirc is a tiny, dependency-light IRC client that follows the
"Keep It Simple, Stupid" (KISS) philosophy. It is intended for
users who prefer a minimal, keyboard-driven IRC experience in a
terminal environment.
Features
--------
* Small, portable C codebase with minimal runtime requirements
* Keyboard-centric editing and navigation
* Simple command and message aliases for fast interaction
* Lightweight build using a standard Makefile
Install
-------
Build and install from source (system-wide install requires
appropriate privileges):
git clone https://github.com/mcpcpc/kirc
cd kirc/
make
sudo make install
Alternatively, run the binary from the repository root after
building: `./kirc`.
Usage
-----
See `man kirc` for a more usage information.
kirc [-s server] [-p port] [-c channels] [-r realname]
[-u username] [-k password] [-a auth] <nickname>
License
-------
See the `LICENSE` file in the repository for details.
================================================
FILE: config.mk
================================================
# config.mk - default build configuration
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
CC = cc
================================================
FILE: include/ansi.h
================================================
/*
* ansi.h
* ANSI escape codes and constants
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_ANSI_H
#define __KIRC_ANSI_H
#define SOH 0x01
#define STX 0x02
#define ETX 0x03
#define EOT 0x04
#define ENQ 0x05
#define ACK 0x06
#define BEL 0x07
#define BS 0x08
#define HT 0x09
#define LF 0x0A
#define VT 0x0B
#define FF 0x0C
#define CR 0x0D
#define SO 0x0E
#define SI 0x0F
#define DLE 0x10
#define DC1 0x11
#define DC2 0x12
#define DC3 0x13
#define DC4 0x14
#define NAK 0x15
#define SYN 0x16
#define ETB 0x17
#define CAN 0x18
#define EM 0x19
#define SUB 0x1A
#define ESC 0x1B
#define FS 0x1C
#define GS 0x1D
#define RS 0x1E
#define US 0x1F
#define DEL 0x7F
#define RESET "\x1b[0m"
#define BOLD "\x1b[1m"
#define DIM "\x1b[2m"
#define ITALIC "\x1b[3m"
#define REVERSE "\x1b[7m"
#define RED "\x1b[31m"
#define GREEN "\x1b[32m"
#define YELLOW "\x1b[33m"
#define BLUE "\x1b[34m"
#define MAGENTA "\x1b[35m"
#define CYAN "\x1b[36m"
#define BOLD_RED "\x1b[1;31m"
#define BOLD_GREEN "\x1b[1;32m"
#define BOLD_YELLOW "\x1b[1;33m"
#define BOLD_BLUE "\x1b[1;34m"
#define BOLD_MAGENTA "\x1b[1;35m"
#define BOLD_CYAN "\x1b[1;36m"
#define CLEAR_LINE "\x1b[0K"
#define CURSOR_HOME "\x1b[H"
#define CURSOR_POS "\x1b[6n"
#endif // __KIRC_ANSI_H
================================================
FILE: include/base64.h
================================================
/*
* base64.h
* Header for base64 encoding module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_BASE64_H
#define __KIRC_BASE64_H
#include "kirc.h"
int base64_encode(char *out, const char *in, size_t in_len);
#endif // __KIRC_BASE64_H
================================================
FILE: include/config.h
================================================
/*
* config.h
* Header for configuration module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_CONFIG_H
#define __KIRC_CONFIG_H
#include "kirc.h"
#include "helper.h"
#include "base64.h"
int config_init(struct kirc_context *ctx);
int config_parse_args(struct kirc_context *ctx, int argc, char *argv[]);
int config_free(struct kirc_context *ctx);
#endif // __KIRC_CONFIG_H
================================================
FILE: include/ctcp.h
================================================
/*
* ctcp.h
* Header for the CTCP module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_CTCP_H
#define __KIRC_CTCP_H
#include "kirc.h"
#include "event.h"
#include "network.h"
#include "output.h"
void ctcp_handle_clientinfo(struct network *network, struct event *event, struct output *output);
void ctcp_handle_ping(struct network *network, struct event *event, struct output *output);
void ctcp_handle_time(struct network *network, struct event *event, struct output *output);
void ctcp_handle_version(struct network *network, struct event *event, struct output *output);
#endif // __KIRC_CTCP_H
================================================
FILE: include/dcc.h
================================================
/*
* dcc.h
* Header for the DCC module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_DCC_H
#define __KIRC_DCC_H
#include "kirc.h"
#include "ansi.h"
#include "helper.h"
#include "event.h"
#include "network.h"
#include "handler.h"
enum dcc_type {
DCC_TYPE_SEND = 0,
DCC_TYPE_RECEIVE
};
enum dcc_state {
DCC_STATE_IDLE = 0,
DCC_STATE_CONNECTING,
DCC_STATE_TRANSFERRING,
DCC_STATE_COMPLETE,
DCC_STATE_ERROR
};
struct dcc_transfer {
enum dcc_type type;
enum dcc_state state;
char filename[NAME_MAX];
char sender[MESSAGE_MAX_LEN];
unsigned long long filesize;
unsigned long long sent;
int file_fd;
};
struct dcc {
struct kirc_context *ctx;
struct pollfd sock_fd[KIRC_DCC_TRANSFERS_MAX];
struct dcc_transfer transfer[KIRC_DCC_TRANSFERS_MAX];
int transfer_count;
};
int dcc_init(struct dcc *dcc, struct kirc_context *ctx);
int dcc_free(struct dcc *dcc);
int dcc_request(struct dcc *dcc, const char *sender,
const char *params);
int dcc_send(struct dcc *dcc, int transfer_id);
int dcc_process(struct dcc *dcc);
int dcc_cancel(struct dcc *dcc, int transfer_id);
void dcc_handle(struct dcc *dcc, struct network *network,
struct event *event);
#endif // __KIRC_DCC_H
================================================
FILE: include/editor.h
================================================
/*
* editor.h
* Header for the editor module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_EDITOR_H
#define __KIRC_EDITOR_H
#include "kirc.h"
#include "terminal.h"
#include "ansi.h"
#include "utf8.h"
enum editor_state {
EDITOR_STATE_NONE = 0,
EDITOR_STATE_SEND,
EDITOR_STATE_TERMINATE
};
struct editor {
struct kirc_context *ctx;
enum editor_state state;
char scratch[MESSAGE_MAX_LEN];
char history[KIRC_HISTORY_SIZE][MESSAGE_MAX_LEN];
int head;
int count;
int position; /* -1 when not browsing history, otherwise index into history[] */
int cursor;
};
char *editor_last_entry(struct editor *editor);
int editor_init(struct editor *editor, struct kirc_context *ctx);
int editor_process_key(struct editor *editor);
int editor_handle(struct editor *editor);
#endif // __KIRC_EDITOR_H
================================================
FILE: include/event.h
================================================
/*
* event.h
* Header for the event module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_EVENT_H
#define __KIRC_EVENT_H
#include "kirc.h"
#include "helper.h"
enum event_type {
EVENT_NONE = 0,
EVENT_CTCP_ACTION,
EVENT_CTCP_CLIENTINFO,
EVENT_CTCP_DCC,
EVENT_CTCP_PING,
EVENT_CTCP_TIME,
EVENT_CTCP_VERSION,
EVENT_ERROR,
EVENT_EXT_CAP,
EVENT_EXT_AUTHENTICATE,
EVENT_JOIN,
EVENT_KICK,
EVENT_MODE,
EVENT_NICK,
EVENT_NOTICE,
EVENT_PART,
EVENT_PING,
EVENT_PRIVMSG,
EVENT_QUIT,
EVENT_TOPIC,
EVENT_001_RPL_WELCOME,
EVENT_002_RPL_YOURHOST,
EVENT_003_RPL_CREATED,
EVENT_004_RPL_MYINFO,
EVENT_005_RPL_BOUNCE,
EVENT_042_RPL_YOURID,
EVENT_200_RPL_TRACELINK,
EVENT_201_RPL_TRACECONNECTING,
EVENT_202_RPL_TRACEHANDSHAKE,
EVENT_203_RPL_TRACEUNKNOWN,
EVENT_204_RPL_TRACEOPERATOR,
EVENT_205_RPL_TRACEUSER,
EVENT_206_RPL_TRACESERVER,
EVENT_207_RPL_TRACESERVICE,
EVENT_208_RPL_TRACENEWTYPE,
EVENT_209_RPL_TRACECLASS,
EVENT_211_RPL_STATSLINKINFO,
EVENT_212_RPL_STATSCOMMANDS,
EVENT_213_RPL_STATSCLINE,
EVENT_215_RPL_STATSILINE,
EVENT_216_RPL_STATSKLINE,
EVENT_218_RPL_STATSYLINE,
EVENT_219_RPL_ENDOFSTATS,
EVENT_221_RPL_UMODEIS,
EVENT_234_RPL_SERVLIST,
EVENT_235_RPL_SERVLISTEND,
EVENT_241_RPL_STATSLLINE,
EVENT_242_RPL_STATSUPTIME,
EVENT_243_RPL_STATSOLINE,
EVENT_244_RPL_STATSHLINE,
EVENT_245_RPL_STATSSLINE,
EVENT_250_RPL_STATSCONN,
EVENT_251_RPL_LUSERCLIENT,
EVENT_252_RPL_LUSEROP,
EVENT_253_RPL_LUSERUNKNOWN,
EVENT_254_RPL_LUSERCHANNELS,
EVENT_255_RPL_LUSERME,
EVENT_256_RPL_ADMINME,
EVENT_257_RPL_ADMINLOC1,
EVENT_258_RPL_ADMINLOC2,
EVENT_259_RPL_ADMINEMAIL,
EVENT_261_RPL_TRACELOG,
EVENT_263_RPL_TRYAGAIN,
EVENT_265_RPL_LOCALUSERS,
EVENT_266_RPL_GLOBALUSERS,
EVENT_300_RPL_NONE,
EVENT_301_RPL_AWAY,
EVENT_302_RPL_USERHOST,
EVENT_303_RPL_ISON,
EVENT_305_RPL_UNAWAY,
EVENT_306_RPL_NOWAWAY,
EVENT_311_RPL_WHOISUSER,
EVENT_312_RPL_WHOISSERVER,
EVENT_313_RPL_WHOISOPERATOR,
EVENT_314_RPL_WHOWASUSER,
EVENT_315_RPL_ENDOFWHO,
EVENT_317_RPL_WHOISIDLE,
EVENT_318_RPL_ENDOFWHOIS,
EVENT_319_RPL_WHOISCHANNELS,
EVENT_322_RPL_LIST,
EVENT_323_RPL_LISTEND,
EVENT_324_RPL_CHANNELMODEIS,
EVENT_328_RPL_CHANNEL_URL,
EVENT_331_RPL_NOTOPIC,
EVENT_332_RPL_TOPIC,
EVENT_333_RPL_TOPICWHOTIME,
EVENT_341_RPL_INVITING,
EVENT_346_RPL_INVITELIST,
EVENT_347_RPL_ENDOFINVITELIST,
EVENT_348_RPL_EXCEPTLIST,
EVENT_349_RPL_ENDOFEXCEPTLIST,
EVENT_351_RPL_VERSION,
EVENT_352_RPL_WHOREPLY,
EVENT_353_RPL_NAMREPLY,
EVENT_364_RPL_LINKS,
EVENT_365_RPL_ENDOFLINKS,
EVENT_366_RPL_ENDOFNAMES,
EVENT_367_RPL_BANLIST,
EVENT_368_RPL_ENDOFBANLIST,
EVENT_369_RPL_ENDOFWHOWAS,
EVENT_371_RPL_INFO,
EVENT_372_RPL_MOTD,
EVENT_374_RPL_ENDOFINFO,
EVENT_375_RPL_MOTDSTART,
EVENT_376_RPL_ENDOFMOTD,
EVENT_381_RPL_YOUREOPER,
EVENT_382_RPL_REHASHING,
EVENT_383_RPL_YOURESERVICE,
EVENT_391_RPL_TIME,
EVENT_392_RPL_USERSSTART,
EVENT_393_RPL_USERS,
EVENT_394_RPL_ENDOFUSERS,
EVENT_395_RPL_NOUSERS,
EVENT_396_RPL_HOSTHIDDEN,
EVENT_400_ERR_UNKNOWNERROR,
EVENT_401_ERR_NOSUCHNICK,
EVENT_402_ERR_NOSUCHSERVER,
EVENT_403_ERR_NOSUCHCHANNEL,
EVENT_404_ERR_CANNOTSENDTOCHAN,
EVENT_405_ERR_TOOMANYCHANNELS,
EVENT_406_ERR_WASNOSUCHNICK,
EVENT_407_ERR_TOOMANYTARGETS,
EVENT_408_ERR_NOSUCHSERVICE,
EVENT_409_ERR_NOORIGIN,
EVENT_411_ERR_NORECIPIENT,
EVENT_412_ERR_NOTEXTTOSEND,
EVENT_413_ERR_NOTOPLEVEL,
EVENT_414_ERR_WILDTOPLEVEL,
EVENT_415_ERR_BADMASK,
EVENT_421_ERR_UNKNOWNCOMMAND,
EVENT_422_ERR_NOMOTD,
EVENT_423_ERR_NOADMININFO,
EVENT_424_ERR_FILEERROR,
EVENT_431_ERR_NONICKNAMEGIVEN,
EVENT_432_ERR_ERRONEUSNICKNAME,
EVENT_433_ERR_NICKNAMEINUSE,
EVENT_436_ERR_NICKCOLLISION,
EVENT_441_ERR_USERNOTINCHANNEL,
EVENT_442_ERR_NOTONCHANNEL,
EVENT_443_ERR_USERONCHANNEL,
EVENT_444_ERR_NOLOGIN,
EVENT_445_ERR_SUMMONDISABLED,
EVENT_446_ERR_USERSDISABLED,
EVENT_451_ERR_NOTREGISTERED,
EVENT_461_ERR_NEEDMOREPARAMS,
EVENT_462_ERR_ALREADYREGISTERED,
EVENT_463_ERR_NOPERMFORHOST,
EVENT_464_ERR_PASSWDMISMATCH,
EVENT_465_ERR_YOUREBANNEDCREEP,
EVENT_467_ERR_KEYSET,
EVENT_470_ERR_LINKCHANNEL,
EVENT_471_ERR_CHANNELISFULL,
EVENT_472_ERR_UNKNOWNMODE,
EVENT_473_ERR_INVITEONLYCHAN,
EVENT_474_ERR_BANNEDFROMCHAN,
EVENT_475_ERR_BADCHANNELKEY,
EVENT_476_ERR_BADCHANMASK,
EVENT_477_ERR_NEEDREGGEDNICK,
EVENT_478_ERR_BANLISTFULL,
EVENT_481_ERR_NOPRIVILEGES,
EVENT_482_ERR_CHANOPRIVSNEEDED,
EVENT_483_ERR_CANTKILLSERVER,
EVENT_485_ERR_UNIQOPRIVSNEEDED,
EVENT_491_ERR_NOOPERHOST,
EVENT_501_ERR_UMODEUNKNOWNFLAG,
EVENT_502_ERR_USERSDONTMATCH,
EVENT_524_ERR_HELPNOTFOUND,
EVENT_704_RPL_HELPSTART,
EVENT_705_RPL_HELPTXT,
EVENT_706_RPL_ENDOFHELP,
EVENT_900_RPL_LOGGEDIN,
EVENT_901_RPL_LOGGEDOUT,
EVENT_902_ERR_NICKLOCKED,
EVENT_903_RPL_SASLSUCCESS,
EVENT_904_ERR_SASLFAIL,
EVENT_905_ERR_SASLTOOLONG,
EVENT_906_ERR_SASLABORTED,
EVENT_907_ERR_SASLALREADY,
EVENT_908_RPL_SASLMECHS
};
struct event_dispatch_table {
const char *command;
enum event_type type;
};
struct event {
struct kirc_context *ctx;
enum event_type type;
char raw[MESSAGE_MAX_LEN];
char channel[CHANNEL_MAX_LEN];
char message[MESSAGE_MAX_LEN];
char command[MESSAGE_MAX_LEN];
char nickname[MESSAGE_MAX_LEN];
char params[MESSAGE_MAX_LEN];
};
int event_init(struct event *event, struct kirc_context *ctx);
int event_parse(struct event *event, char *line);
#endif // __KIRC_EVENT_H
================================================
FILE: include/handler.h
================================================
/*
* handler.h
* Header for handler dispatch module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_HANDLER_H
#define __KIRC_HANDLER_H
#include "kirc.h"
#include "event.h"
#include "network.h"
#include "output.h"
typedef void (*event_handler_fn)(struct network *network, struct event *event, struct output *output);
struct handler {
struct kirc_context *ctx;
event_handler_fn handlers[KIRC_EVENT_TYPE_MAX];
event_handler_fn default_handler;
};
void handler_default(struct handler *handler, event_handler_fn handler_fn);
void handler_register(struct handler *handler, enum event_type type, event_handler_fn handler_fn);
void handler_dispatch(struct handler *handler, struct network *network, struct event *event, struct output *output);
int handler_init(struct handler *handler, struct kirc_context *ctx);
#endif // __KIRC_HANDLER_H
================================================
FILE: include/helper.h
================================================
/*
* helper.h
* Header for the helper module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_HELPER_H
#define __KIRC_HELPER_H
#include "kirc.h"
int safecpy(char *s1, const char *s2, size_t n);
int memzero(void *s, size_t n);
char *find_message_end(const char *buffer, size_t len);
#endif // __KIRC_HELPER_H
================================================
FILE: include/kirc.h
================================================
/*
* kirc.h
* Main header file for kirc IRC client
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_H
#define __KIRC_H
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <netdb.h>
#include <poll.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#ifndef NAME_MAX
#define NAME_MAX 255
#endif
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 255
#endif
#define CHANNEL_MAX_LEN 200 /* per RFC1459 */
#define MESSAGE_MAX_LEN 512 /* per RFC1459 */
#define AUTH_CHUNK_SIZE 400 /* per IRCv3.1 */
#define KIRC_VERSION_MAJOR "1"
#define KIRC_VERSION_MINOR "2"
#define KIRC_VERSION_PATCH "2"
#define KIRC_CHANNEL_LIMIT 256
#define KIRC_DCC_BUFFER_SIZE 8192
#define KIRC_DCC_TRANSFERS_MAX 16
#define KIRC_EVENT_TYPE_MAX 256
#define KIRC_HANDLER_MAX_ENTRIES 256
#define KIRC_HISTORY_SIZE 64
#define KIRC_OUTPUT_BUFFER_SIZE 8192
#define KIRC_PORT_RANGE_MAX 65535
#define KIRC_TAB_WIDTH 4
#define KIRC_TIMEOUT_MS 5000
#define KIRC_TIMESTAMP_SIZE 6
#define KIRC_TIMESTAMP_FORMAT "%H:%M"
#define KIRC_DEFAULT_COLUMNS 80
#define KIRC_DEFAULT_PORT "6667"
#define KIRC_DEFAULT_SERVER "irc.libera.chat"
enum sasl_mechanism {
SASL_NONE = 0,
SASL_PLAIN,
SASL_EXTERNAL
};
struct kirc_context {
char server[HOST_NAME_MAX];
char port[6];
char nickname[MESSAGE_MAX_LEN];
char realname[MESSAGE_MAX_LEN];
char username[MESSAGE_MAX_LEN];
char password[MESSAGE_MAX_LEN];
char channels[KIRC_CHANNEL_LIMIT][CHANNEL_MAX_LEN];
char target[KIRC_CHANNEL_LIMIT];
char auth[MESSAGE_MAX_LEN];
enum sasl_mechanism mechanism;
};
#endif // __KIRC_H
================================================
FILE: include/network.h
================================================
/*
* network.h
* Header for the network module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_NETWORK_H
#define __KIRC_NETWORK_H
#include "kirc.h"
#include "transport.h"
#include "ansi.h"
#include "helper.h"
#include "output.h"
struct network {
struct kirc_context *ctx;
struct transport *transport;
char buffer[MESSAGE_MAX_LEN];
int len;
};
int network_send(struct network *network, const char *fmt, ...);
int network_receive(struct network *network);
int network_connect(struct network *network);
int network_command_handler(struct network *network, char *msg, struct output *output);
int network_send_credentials(struct network *network);
int network_init(struct network *network,
struct transport *transport, struct kirc_context *ctx);
int network_free(struct network *network);
#endif // __KIRC_NETWORK_H
================================================
FILE: include/output.h
================================================
/*
* output.h
* Header for the buffered output module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_OUTPUT_H
#define __KIRC_OUTPUT_H
#include "kirc.h"
#include "ansi.h"
struct output {
struct kirc_context *ctx;
char buffer[KIRC_OUTPUT_BUFFER_SIZE];
int len;
};
int output_init(struct output *output,
struct kirc_context *ctx);
int output_append(struct output *output,
const char *fmt, ...);
void output_flush(struct output *output);
void output_clear(struct output *output);
int output_pending(struct output *output);
#endif // __KIRC_OUTPUT_H
================================================
FILE: include/protocol.h
================================================
/*
* protocol.h
* Header for the protocol module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_PROTOCOL_H
#define __KIRC_PROTOCOL_H
#include "kirc.h"
#include "ansi.h"
#include "event.h"
#include "helper.h"
#include "network.h"
#include "output.h"
void protocol_noop(struct network *network, struct event *event, struct output *output);
void protocol_ping(struct network *network, struct event *event, struct output *output);
void protocol_authenticate(struct network *network, struct event *event, struct output *output);
void protocol_welcome(struct network *network, struct event *event, struct output *output);
void protocol_raw(struct network *network, struct event *event, struct output *output);
void protocol_info(struct network *network, struct event *event, struct output *output);
void protocol_error(struct network *network, struct event *event, struct output *output);
void protocol_notice(struct network *network, struct event *event, struct output *output);
void protocol_privmsg(struct network *network, struct event *event, struct output *output);
void protocol_nick(struct network *network, struct event *event, struct output *output);
void protocol_join(struct network *network, struct event *event, struct output *output);
void protocol_part(struct network *network, struct event *event, struct output *output);
void protocol_ctcp_action(struct network *network, struct event *event, struct output *output);
void protocol_ctcp_info(struct network *network, struct event *event, struct output *output);
#endif // __KIRC_PROTOCOL_H
================================================
FILE: include/terminal.h
================================================
/*
* terminal.h
* Header for the terminal module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_TERMINAL_H
#define __KIRC_TERMINAL_H
#include "kirc.h"
#include "ansi.h"
struct terminal {
struct kirc_context *ctx;
struct termios original;
int raw_mode_enabled;
};
int terminal_columns(int tty_fd);
int terminal_init(struct terminal *terminal,
struct kirc_context *ctx);
int terminal_enable_raw(struct terminal *terminal);
void terminal_disable_raw(struct terminal *terminal);
#endif // __KIRC_TERMINAL_H
================================================
FILE: include/transport.h
================================================
/*
* transport.h
* Header for the transport module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_TRANSPORT_H
#define __KIRC_TRANSPORT_H
#include "kirc.h"
struct transport {
struct kirc_context *ctx;
int fd;
};
ssize_t transport_send(struct transport *transport,
const char *buffer, size_t len);
ssize_t transport_receive(struct transport *transport,
char *buffer, size_t len);
int transport_connect(struct transport *transport);
int transport_init(struct transport *transport,
struct kirc_context *ctx);
int transport_free(struct transport *transport);
#endif // __KIRC_TRANSPORT_H
================================================
FILE: include/utf8.h
================================================
/*
* utf8.h
* Header for the UTF-8 module
* Author: Michael Czigler
* License: MIT
*/
#ifndef __KIRC_UTF8_H
#define __KIRC_UTF8_H
#include "kirc.h"
int utf8_prev_char_len(const char *s, int pos);
int utf8_next_char_len(const char *s, int pos, int maxlen);
int utf8_validate(const char *s, int len);
#endif // __KIRC_UTF8_H
================================================
FILE: kirc.1
================================================
.TH KIRC 1 "December 2025"
.SH NAME
kirc \- a tiny, dependency-light IRC client
.SH SYNOPSIS
.B kirc
.RB [\-s " server"]
.RB [\-p " port"]
.RB [\-c " channel(s)"]
.RB [\-r " realname"]
.RB [\-u " username"]
.RB [\-k " password"]
.RB [\-a " auth"]
.RB <nickname>
.SH DESCRIPTION
.B kirc
is an extremely fast and simple Internet Relay Chat (IRC) client designed with
portability and minimalism in mind, following the "Keep It Simple, Stupid" (KISS)
philosophy. It provides a keyboard-driven interface for real-time IRC communication
and is optimized to run on systems with minimal dependencies, making it suitable for
embedded systems, lightweight containers, and resource-constrained environments.
.PP
The primary design principle of
.B kirc
is to maintain a small codebase while providing essential IRC functionality. The
client follows the Unix philosophy of doing one thing well: it reads from STDIN
and prints to STDOUT, allowing all traffic to be easily multiplexed and processed
through external commands and shell pipelines. This design enables powerful text
filtering, logging, and integration with other Unix tools.
.PP
All highlighted text and color can be controlled with ANSI escape sequences,
providing full control over output styling and appearance. The client is compatible
with both traditional and modern terminal emulators.
.PP
For best results and optimal rendering with full color support, use a terminal
emulator with 256 color support, such as xterm-256color, GNOME Terminal, Konsole,
or iTerm2. Most modern terminal emulators will provide a pleasant experience with
.B kirc
with default settings.
.SH OPTIONS
The following options are available. These can also be set using environment
variables (see ENVIRONMENT section), which will be overridden by any command-line
arguments. The nickname argument is always required.
.TP
.BI \-s " server"
Specifies the IRC server hostname or IP address to connect to. The server name is
passed directly to the network layer without validation, so ensure it is correct
before connecting.
.br
Default: irc.libera.chat
.TP
.BI \-p " port"
Specifies the TCP port number used to connect to the IRC server. Standard IRC
ports are 6667 (plaintext) and 6697 (with TLS/SSL). Ensure your firewall permits
outbound connections on the specified port.
.br
Default: 6667
.TP
.BI \-c " channel(s)"
Specifies the channel(s) to JOIN upon successful connection. Multiple channels can
be specified as a comma-separated or pipe-separated list without spaces
(e.g., "#channel1,#channel2"). The first channel in the list will be set as the
default target for messages sent without an explicit destination. Channel names
must conform to IRC specifications (typically starting with # or &).
.TP
.BI \-r " realname"
Specifies the user's real name as sent during the IRC connection handshake. This
value is visible in WHOIS queries and other user information lookups performed by
other users. If not specified, the client defaults to using the nickname. This can
be any string up to 63 characters.
.TP
.BI \-u " username"
Specifies the USER connection username, which becomes part of your user's hostmask
(e.g., user@host). This is displayed in various IRC commands and messages. If not
specified, the client defaults to using the nickname. This should typically be
alphanumeric characters without spaces.
.TP
.BI \-k " password"
Specifies the plain text PASS connection value, typically used for authentication
with the server or services. This is sent during the initial connection phase.
Be cautious with this option as the password is visible in the command history and
process list. For sensitive configurations, consider using the KIRC_PASSWORD
environment variable instead.
.TP
.BI \-a " auth"
Specifies SASL (Simple Authentication and Security Layer) authentication mechanism
and token value to be used for connection authentication. The argument value is
comprised of a colon-delimited string, where the first token is the authentication
mechanism (e.g., "PLAIN", "EXTERNAL", "SCRAM-SHA-256", etc.) and the second string
is the mechanism-specific authentication credential (typically BASE64 encoded).
For the PLAIN mechanism, you can provide either a pre-encoded BASE64 token or three
colon-separated values (authzid:authcid:passwd) which will be automatically encoded.
.br
Example: "PLAIN:amlsbGVzAGppbGxlcwBzZXNhbWU=" or "PLAIN:alice:alice:password"
.SH EXIT STATUS
.TP
.B 0
Successful program execution and normal client termination.
.TP
.B 1
Usage error, syntax error, connection failure, or other operational error. Check
the terminal output for specific error messages to diagnose the issue.
.SH ENVIRONMENT
The following environment variables can be used to set defaults for connection
parameters. All environment variables will be overridden by any corresponding
command-line options. This is useful for creating shell aliases or scripts that
establish connections with specific parameters.
.TP
.B KIRC_SERVER
Default IRC server hostname or IP address. Equivalent to the
.BI \-s
option.
.TP
.B KIRC_PORT
Default server port number. Equivalent to the
.BI \-p
option.
.TP
.B KIRC_CHANNELS
Default channels to join upon connection (comma or pipe separated). Equivalent to
the
.BI \-c
option.
.TP
.B KIRC_REALNAME
Default real name to use in WHOIS and other user information. Equivalent to the
.BI \-r
option.
.TP
.B KIRC_USERNAME
Default username for the connection. Equivalent to the
.BI \-u
option.
.TP
.B KIRC_PASSWORD
Default password for connection authentication. Equivalent to the
.BI \-k
option. Be cautious storing sensitive information in environment variables.
.TP
.B KIRC_AUTH
Default SASL authentication token and mechanism. Equivalent to the
.BI \-a
option.
.SH COMMANDS
Once connected to an IRC server,
.B kirc
supports the following commands. Commands are entered in the input line and
executed by pressing ENTER. Text entered without a command prefix is treated as
a message to the current target.
.TP
.BI /<command>
Send a raw IRC command to the server. This allows direct access to all IRC
protocol commands. Examples include /JOIN, /PART, /WHOIS, /NICK, /KICK, /BAN,
/TOPIC, /MODE, /INVITE, and many others. Refer to IRC protocol documentation
(RFC 1459) for a complete list of supported commands. The client does not
validate or pre-process raw commands, so ensure correct syntax before sending.
.TP
.BI /set " <target>"
Set the default message channel or nickname to
.I <target>.
All subsequent messages entered without an explicit target will be sent to this
channel or user. The current target is typically displayed in the status line or
prompt area.
.TP
.BI /topic " <channel> [<topic>]"
Query or set the topic for the specified
.I <channel>.
When invoked with only a channel name, the command requests and displays the
current channel topic. When a
.I <topic>
string is provided, it sets the channel topic to the specified text (subject to
channel mode restrictions and operator privileges).
.TP
.BI <message>
Send a PRIVMSG to the currently set target (channel or nickname) with
.I <message>
as the message content. This is the primary way to communicate in IRC. Messages
are displayed to all users in the target channel or privately to the target user.
.TP
.BI @<target> " <message>"
Send a PRIVMSG to a specified
.I <target>
(channel or nickname) with
.I <message>
as the content, without changing the default target. This allows sending one-off
messages to other destinations while maintaining your primary conversation context.
.TP
.BI /me " <action>"
Send a CTCP ACTION command to the currently set target. This displays your message
as an action (e.g., "* alice does something") to all users in the target channel
or to the target user. Commonly used for expressing actions or emotions.
.TP
.BI /ctcp " <target> <command> [<arguments>...]"
Send a raw Client-to-Client Protocol (CTCP) command to the specified
.I <target>
nickname or channel. Common CTCP commands include "CLIENTINFO", "TIME", "VERSION",
"PING", and "DCC". Arguments are separated by spaces. CTCP commands are typically
used for client discovery and file transfers (see DCC FILE TRANSFERS section).
.SH KEY BINDINGS
.B kirc
provides standard readline-style key bindings for line editing and command history
navigation. The following key combinations are supported:
.TP
.B CTRL+C
Force quit
.B kirc
immediately, disconnecting from the server without sending a QUIT command. This
is useful for emergency termination or when the normal exit mechanism is unresponsive.
.TP
.B CTRL+U
Delete the entire current input line, clearing all typed text and returning to an
empty prompt.
.TP
.B Arrow Left (or CTRL+B)
Move cursor one character to the left.
.TP
.B Arrow Right (or CTRL+F)
Move cursor one character to the right.
.TP
.B Arrow Up (or CTRL+P)
Navigate backwards through command history, displaying the previous command entered.
Pressing multiple times cycles through older commands.
.TP
.B Arrow Down (or CTRL+N)
Navigate forwards through command history, displaying the next (more recent) command
entered. Pressing at the end of history returns to the empty prompt.
.TP
.B Home (or CTRL+A)
Move cursor to the beginning of the current input line.
.TP
.B End (or CTRL+E)
Move cursor to the end of the current input line.
.TP
.B Delete
Delete the character at the current cursor position.
.TP
.B Backspace (or CTRL+H)
Delete the character immediately before the cursor position and move the cursor
back one character.
.SH EXAMPLES
.SS Basic usage
.TP
.B kirc alice
Connect with nickname 'alice' to the default Libera.Chat server (irc.libera.chat:6667).
The client will use 'alice' as the username and real name if not otherwise specified.
.SS Advanced connection parameters
.TP
.B kirc \-s irc.example.org \-p 6667 \-c "#foo,#bar" alice
Connect to a custom IRC server (irc.example.org) on port 6667 and automatically join
the channels #foo and #bar upon connection. The #foo channel becomes the default
message target.
.SS Environment variables
.TP
.B KIRC_SERVER=irc.libera.chat KIRC_PORT=6697 kirc alice
Use environment variables to set the server and port for this session. These values
can be permanently set in shell profile files for convenience.
.SS Logging
.TP
.B kirc alice | tee -a kirc-$(date +%Y%m%d).log
Connect and simultaneously save all output to a log file named with the current date
(e.g., kirc-20251229.log). The
.B tee
command duplicates output while the
.B \\-a
flag appends to the file rather than overwriting it.
.SS SASL authentication
.TP
.B kirc \-a "PLAIN:alice:alice:password" alice
Connect using SASL PLAIN authentication with automatic token encoding. The client
will automatically encode the authzid:authcid:passwd values into the proper BASE64
format required by the SASL PLAIN mechanism.
.TP
.B kirc \-a \\[dq]PLAIN:$(printf \[aq]%s\0%s\0%s\[aq] \[aq]alice\[aq] \[aq]alice\[aq] \[aq]password\[aq] | base64)\\[dq] alice
Alternatively, generate a BASE64-encoded SASL PLAIN authentication token manually
and connect using SASL authentication. The token format is "username\0username\0password"
(with NULL bytes represented as \\0) encoded in BASE64. This demonstrates manual token
generation for the SASL PLAIN mechanism.
.SS TLS/SSL connections via socat
.TP
.B socat tcp-listen:6667,reuseaddr,fork,bind=127.0.0.1 ssl:irc.example.org:6697 & kirc \-s 127.0.0.1 alice
Establish a TLS/SSL encrypted connection to irc.example.org by proxying through socat.
Socat listens on local port 6667 and forwards all traffic through an encrypted channel
to the remote server's port 6697. This approach is useful when
.B kirc
doesn't have native TLS support or when additional connection filtering is needed.
.SS Proxy connections via socat
.TP
.B socat tcp-listen:6667,fork,reuseaddr,bind=127.0.0.1 proxy:<proxyurl>:irc.example.org:6667,proxyport=<proxyport> & kirc \-s 127.0.0.1 \-p 6667 alice
Connect to irc.example.org through an HTTP/HTTPS proxy server using socat as an
intermediary. Replace <proxyurl> and <proxyport> with your actual proxy server
details. This technique allows IRC access from networks with strict outbound
connection policies.
.SH DCC FILE TRANSFERS
.SS Overview
.B kirc
supports Direct Client-to-Client (DCC) file transfers, including XDCC file requests
commonly used on IRC file servers. DCC transfers occur directly between IRC clients
without passing through the IRC server, making them independent of server bandwidth
limitations.
.SS Receiving files
When another IRC user sends you a file via DCC SEND,
.B kirc
automatically detects the incoming file transfer request and begins downloading the
file to the current working directory. The client will display a status message:
.PP
.RS
.nf
dcc: receiving <filename> from <sender> (<size> bytes)
.fi
.RE
.PP
Transfer progress and completion messages will appear as the file downloads.
The file is saved with its original filename. If a file with the same name exists,
the behavior depends on the client configuration. Files are saved relative to the
current working directory from which
.B kirc
was launched.
.SS XDCC requests
To request a file from an XDCC bot, use the standard XDCC request format:
.PP
.RS
.nf
/ctcp <botname> XDCC SEND #<packet_number>
.fi
.RE
.PP
The bot will initiate a DCC SEND transfer.
.B kirc
automatically handles the incoming transfer and begins downloading the requested file.
Multiple XDCC requests can be queued with a single bot, and the bot will process them
sequentially.
.SS Limitations
.B kirc
supports up to 16 simultaneous DCC transfers. Exceeding this limit will reject
additional transfer requests. Files are always saved to the current working directory
with their original filenames. Ensure adequate disk space is available before initiating
large file transfers.
.SH SEE ALSO
.BR irc (1),
.BR socat (1)
.SH STANDARDS
.B kirc
implements the Internet Relay Chat protocol as defined in RFC 1459, RFC 2222 (SASL),
and IRCv3.1 specifications. The client supports CTCP (Client-to-Client Protocol)
extensions for client-to-client communication and file transfers.
.SH BUGS
https://github.com/mcpcpc/kirc/issues
================================================
FILE: src/base64.c
================================================
/*
* base64.c
* Base64 encoding handlers
* Author: Michael Czigler
* License: MIT
*/
#include "base64.h"
/**
* base64_encode() - Encode data to base64 format
* @out: Output buffer to store the base64-encoded string
* @in: Input data buffer to encode
* @in_len: Length of the input data in bytes
*
* Encodes binary data into base64 ASCII representation. The output buffer
* must be large enough to hold the encoded data (use base64_encoded_size()
* to determine the required size). Uses the standard base64 alphabet with
* '+' and '/' characters.
*
* Return: 0 on success, -1 if input/output is NULL or input length is 0
*/
int base64_encode(char *out, const char *in, size_t in_len)
{
size_t i, j, v;
char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
if (in == NULL || in_len == 0 || out == NULL) {
return -1;
}
for (i = 0, j = 0; i < in_len; i += 3, j += 4) {
v = in[i];
v = (i + 1) < in_len ? v << 8 | in[i + 1] : v << 8;
v = (i + 2) < in_len ? v << 8 | in[i + 2] : v << 8;
out[j] = base64_table[(v >> 18) & 0x3F];
out[j + 1] = base64_table[(v >> 12) & 0x3F];
if ((i + 1) < in_len) {
out[j + 2] = base64_table[(v >> 6) & 0x3F];
} else {
out[j + 2] = '=';
}
if ((i + 2) < in_len) {
out[j + 3] = base64_table[v & 0x3F];
} else {
out[j + 3] = '=';
}
}
return 0;
}
================================================
FILE: src/config.c
================================================
/*
* config.c
* Configuration parsing and initialization
* Author: Michael Czigler
* License: MIT
*/
#include "config.h"
/**
* config_validate_port() - Validate a port number string
* @value: String representation of the port number
*
* Validates that the given string is a valid port number. Checks for
* non-numeric characters, range limits, and conversion errors.
*
* Return: 0 if valid port, -1 if invalid or out of range
*/
static int config_validate_port(const char *value)
{
if ((value == NULL) || (*value == '\0')) {
return -1;
}
/* Check for invalid chars before conversion */
for (const char *p = value; *p != '\0'; ++p) {
if ((*p < '0') || (*p > '9')) {
return -1;
}
}
errno = 0;
char *endptr;
long port = strtol(value, &endptr, 10);
if ((endptr == value) || (*endptr != '\0')) {
return -1;
}
if (errno == ERANGE) {
return -1;
}
if ((port > KIRC_PORT_RANGE_MAX) || (port < 0)) {
return -1;
}
return 0;
}
/**
* config_parse_channels() - Parse comma or pipe-separated channel list
* @ctx: IRC context structure to store parsed channels
* @value: String containing channel names separated by ',' or '|'
*
* Parses a string of channel names and stores them in the context structure.
* Supports both comma and pipe delimiters. Limited to KIRC_CHANNEL_LIMIT
* channels.
*/
static void config_parse_channels(struct kirc_context *ctx, char *value)
{
char *tok = NULL;
size_t idx = 0;
for (tok = strtok(value, ",|"); tok != NULL && idx < KIRC_CHANNEL_LIMIT;
tok = strtok(NULL, ",|")) {
safecpy(ctx->channels[idx], tok, sizeof(ctx->channels[idx]));
idx++;
}
}
/**
* config_parse_mechanism() - Parse SASL authentication mechanism
* @ctx: IRC context structure to store authentication settings
* @value: String containing mechanism and credentials
*
* Parses SASL authentication mechanism (EXTERNAL or PLAIN) with optional
* credentials. For PLAIN, expects format "PLAIN:authzid:authcid:password"
* and base64-encodes the credentials. For EXTERNAL, no additional data needed.
*/
static void config_parse_mechanism(struct kirc_context *ctx, char *value)
{
char *mechanism = strtok(value, ":");
if (mechanism == NULL) {
return;
}
if (strcmp(mechanism, "EXTERNAL") == 0) {
ctx->mechanism = SASL_EXTERNAL;
return;
} else if (strcmp(mechanism, "PLAIN") == 0) {
ctx->mechanism = SASL_PLAIN;
} else {
return; /* invalid mechanism */
}
char *token = strtok(NULL, "");
if (token == NULL) {
return; /* invalid token */
}
int count = 0;
for (int i = 0; token[i] != '\0'; ++i) {
if (token[i] == ':') count++;
}
if (count == 2) {
size_t plain_len = 0;
char plain[MESSAGE_MAX_LEN];
char *p = plain;
char *authzid = strtok(token, ":");
if (authzid == NULL) {
return;
}
size_t authzid_len = strnlen(authzid, 255);
memcpy(p, authzid, authzid_len);
p += authzid_len;
*p++ = '\0';
plain_len += authzid_len + 1;
char *authcid = strtok(NULL, ":");
if (authcid == NULL) {
return;
}
size_t authcid_len = strnlen(authcid, 255);
memcpy(p, authcid, authcid_len);
p += authcid_len;
*p++ = '\0';
plain_len += authcid_len + 1;
char *passwd = strtok(NULL, "");
if (passwd == NULL) {
return;
}
size_t passwd_len = strnlen(passwd, 255);
memcpy(p, passwd, passwd_len);
plain_len += passwd_len;
base64_encode(ctx->auth, plain, plain_len);
memzero(plain, sizeof(plain));
} else {
safecpy(ctx->auth, token, sizeof(ctx->auth));
}
}
/**
* config_apply_env() - Apply environment variable to configuration
* @ctx: IRC context structure (unused)
* @env_name: Name of the environment variable to read
* @dest: Destination buffer for the value
* @dest_size: Size of the destination buffer
*
* Reads an environment variable and copies its value to the destination
* buffer if the variable exists and is non-empty.
*
* Return: 0 on success or if variable doesn't exist, error code from safecpy
*/
static int config_apply_env(struct kirc_context *ctx, const char *env_name,
char *dest, size_t dest_size)
{
char *env = getenv(env_name);
if (env && *env) {
return safecpy(dest, env, dest_size);
}
return 0;
}
/**
* config_init() - Initialize configuration with defaults and environment
* @ctx: IRC context structure to initialize
*
* Initializes the configuration context with default values and applies
* settings from environment variables (KIRC_SERVER, KIRC_PORT, KIRC_CHANNELS,
* KIRC_REALNAME, KIRC_USERNAME, KIRC_PASSWORD, KIRC_AUTH). Validates port
* numbers and parses authentication mechanisms.
*
* Return: 0 on success, -1 if port validation fails
*/
int config_init(struct kirc_context *ctx)
{
memset(ctx, 0, sizeof(*ctx));
safecpy(ctx->server, KIRC_DEFAULT_SERVER,
sizeof(ctx->server));
safecpy(ctx->port, KIRC_DEFAULT_PORT,
sizeof(ctx->port));
ctx->mechanism = SASL_NONE;
config_apply_env(ctx, "KIRC_SERVER", ctx->server, sizeof(ctx->server));
char *env_port = getenv("KIRC_PORT");
if (env_port && *env_port) {
if (config_validate_port(env_port) < 0) {
fprintf(stderr, "invalid port number in KIRC_PORT\n");
return -1;
}
safecpy(ctx->port, env_port, sizeof(ctx->port));
}
char *env_channels = getenv("KIRC_CHANNELS");
if (env_channels && *env_channels) {
config_parse_channels(ctx, env_channels);
}
config_apply_env(ctx, "KIRC_REALNAME", ctx->realname,
sizeof(ctx->realname));
config_apply_env(ctx, "KIRC_USERNAME", ctx->username,
sizeof(ctx->username));
config_apply_env(ctx, "KIRC_PASSWORD", ctx->password,
sizeof(ctx->password));
char *env_auth = getenv("KIRC_AUTH");
if (env_auth && *env_auth) {
config_parse_mechanism(ctx, env_auth);
}
return 0;
}
/**
* config_parse_args() - Parse command-line arguments
* @ctx: IRC context structure to populate with parsed values
* @argc: Argument count
* @argv: Argument vector
*
* Parses command-line options using getopt. Supports:
* -s server, -p port, -r realname, -u username, -k password,
* -c channels, -a auth_mechanism
* The nickname is required as a positional argument.
*
* Return: 0 on success, -1 on error or invalid arguments
*/
int config_parse_args(struct kirc_context *ctx, int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "%s: no arguments\n", argv[0]);
return -1;
}
int opt;
while ((opt = getopt(argc, argv, "s:p:r:u:k:c:a:")) > 0) {
switch (opt) {
case 's': /* server */
safecpy(ctx->server, optarg, sizeof(ctx->server));
break;
case 'p': /* port */
if (config_validate_port(optarg) < 0) {
fprintf(stderr, "invalid port number\n");
return -1;
}
safecpy(ctx->port, optarg, sizeof(ctx->port));
break;
case 'r': /* realname */
safecpy(ctx->realname, optarg, sizeof(ctx->realname));
break;
case 'u': /* username */
safecpy(ctx->username, optarg, sizeof(ctx->username));
break;
case 'k': /* password */
safecpy(ctx->password, optarg, sizeof(ctx->password));
break;
case 'c': /* channel(s) */
config_parse_channels(ctx, optarg);
break;
case 'a': /* SASL authentication */
config_parse_mechanism(ctx, optarg);
break;
case ':':
fprintf(stderr, "%s: missing -%c value\n", argv[0], opt);
return -1;
case '?':
fprintf(stderr, "%s: unknown argument\n", argv[0]);
return -1;
default:
return -1;
}
}
if (optind >= argc) {
fprintf(stderr, "nickname not specified\n");
return -1;
}
size_t nickname_n = sizeof(ctx->nickname);
safecpy(ctx->nickname, argv[optind], nickname_n);
return 0;
}
/**
* config_free() - Securely clear sensitive configuration data
* @ctx: IRC context structure to clean up
*
* Securely zeroes out sensitive fields (auth token and password) from the
* configuration context to prevent them from remaining in memory.
*
* Return: 0 on success, -1 if secure clearing fails
*/
int config_free(struct kirc_context *ctx)
{
if (memzero(ctx->auth, sizeof(ctx->auth)) < 0) {
fprintf(stderr, "auth token value not safely cleared\n");
return -1;
}
if (memzero(ctx->password, sizeof(ctx->password)) < 0) {
fprintf(stderr, "password value not safely cleared\n");
return -1;
}
return 0;
}
================================================
FILE: src/ctcp.c
================================================
/*
* ctcp.c
* Client-to-client protocol (CTCP) event handling
* Author: Michael Czigler
* License: MIT
*/
#include "ctcp.h"
/**
* ctcp_handle_clientinfo() - Handle CTCP CLIENTINFO request
* @network: Network connection structure
* @event: Event structure containing request details
* @output: Output buffer structure (unused)
*
* Responds to CTCP CLIENTINFO queries with a list of supported CTCP
* commands: PING, ACTION, CLIENTINFO, DCC, PING, TIME, VERSION.
* Only responds to PRIVMSG, not NOTICE.
*/
void ctcp_handle_clientinfo(struct network *network, struct event *event, struct output *output)
{
(void)output;
const char *command = event->command;
const char *nickname = event->nickname;
if (strcmp(command, "PRIVMSG") == 0) {
network_send(network,
"NOTICE %s :\001PING ACTION CLIENTINFO DCC PING TIME VERSION\001\r\n",
nickname);
}
}
/**
* ctcp_handle_ping() - Handle CTCP PING request
* @network: Network connection structure
* @event: Event structure containing ping data
* @output: Output buffer structure (unused)
*
* Responds to CTCP PING queries by echoing back any provided arguments.
* Only responds to PRIVMSG, not NOTICE.
*/
void ctcp_handle_ping(struct network *network, struct event *event, struct output *output)
{
(void)output;
const char *command = event->command;
const char *nickname = event->nickname;
const char *message = event->message;
if (strcmp(command, "PRIVMSG") == 0) {
if (message[0] != '\0') {
network_send(network,
"NOTICE %s :\001PING %s\001\r\n",
nickname, message);
} else {
network_send(network,
"NOTICE %s :\001PING\001\r\n",
nickname);
}
}
}
/**
* ctcp_handle_time() - Handle CTCP TIME request
* @network: Network connection structure
* @event: Event structure containing request details
* @output: Output buffer structure (unused)
*
* Responds to CTCP TIME queries with the current local time in a formatted
* string. Only responds to PRIVMSG, not NOTICE.
*/
void ctcp_handle_time(struct network *network, struct event *event, struct output *output)
{
(void)output;
const char *command = event->command;
const char *nickname = event->nickname;
if (strcmp(command, "PRIVMSG") == 0) {
char tbuf[128];
time_t now;
time(&now);
struct tm *info = localtime(&now);
strftime(tbuf, sizeof(tbuf), "%c", info);
network_send(network,
"NOTICE %s :\001TIME %s\001\r\n",
nickname, tbuf);
}
}
/**
* ctcp_handle_version() - Handle CTCP VERSION request
* @network: Network connection structure
* @event: Event structure containing request details
* @output: Output buffer structure (unused)
*
* Responds to CTCP VERSION queries with the client name and version number.
* Only responds to PRIVMSG, not NOTICE.
*/
void ctcp_handle_version(struct network *network, struct event *event, struct output *output)
{
(void)output;
const char *command = event->command;
const char *nickname = event->nickname;
if (strcmp(command, "PRIVMSG") == 0) {
network_send(network,
"NOTICE %s :\001VERSION kirc %s\001\r\n", nickname,
KIRC_VERSION_MAJOR "." KIRC_VERSION_MINOR "." KIRC_VERSION_PATCH);
}
}
================================================
FILE: src/dcc.c
================================================
/*
* dcc.c
* Direct client-to-client (DCC) event handling
* Author: Michael Czigler
* License: MIT
*/
#include "dcc.h"
/**
* sanitize_filename() - Validate filename for security
* @filename: Filename string to validate
*
* Checks a filename for security issues: rejects empty names, dotfiles,
* path traversal attempts (..), path separators (/ or \\), and names
* exceeding NAME_MAX length.
*
* Return: 0 if filename is safe, -1 if unsafe or invalid
*/
static int sanitize_filename(char *filename)
{
if (filename == NULL) {
return -1;
}
if ((filename[0] == '\0') || (filename[0] == '.')) {
return -1;
}
if (strstr(filename, "..") != NULL) {
return -1;
}
if (strchr(filename, '/') != NULL) {
return -1;
}
if (strchr(filename, '\\') != NULL) {
return -1;
}
if (strlen(filename) >= NAME_MAX) {
return -1;
}
return 0;
}
/**
* dcc_init() - Initialize DCC transfer management structure
* @dcc: DCC structure to initialize
* @ctx: IRC context structure
*
* Initializes the DCC transfer manager, setting all file descriptors to -1
* and transfer states to idle. Prepares the structure for handling up to
* KIRC_DCC_TRANSFERS_MAX concurrent transfers.
*
* Return: 0 on success, -1 if dcc or ctx is NULL
*/
int dcc_init(struct dcc *dcc, struct kirc_context *ctx)
{
if ((dcc == NULL) || (ctx == NULL)) {
return -1;
}
memset(dcc, 0, sizeof(*dcc));
dcc->ctx = ctx;
int limit = KIRC_DCC_TRANSFERS_MAX;
for (int i = 0; i < limit; ++i) {
dcc->sock_fd[i].fd = -1;
dcc->sock_fd[i].events = POLLIN;
dcc->transfer[i].state = DCC_STATE_IDLE;
dcc->transfer[i].file_fd = -1;
}
return 0;
}
/**
* dcc_free() - Free DCC resources and close connections
* @dcc: DCC structure to clean up
*
* Closes all open socket and file descriptors associated with DCC transfers.
* Should be called before program termination to release resources.
*
* Return: 0 on success, -1 if dcc is NULL
*/
int dcc_free(struct dcc *dcc)
{
if (dcc == NULL) {
return -1;
}
int limit = KIRC_DCC_TRANSFERS_MAX;
for (int i = 0; i < limit; ++i) {
if (dcc->sock_fd[i].fd >= 0) {
close(dcc->sock_fd[i].fd);
dcc->sock_fd[i].fd = -1;
}
if (dcc->transfer[i].file_fd >= 0) {
close(dcc->transfer[i].file_fd);
dcc->transfer[i].file_fd = -1;
}
}
return 0;
}
/**
* dcc_send() - Send data for an active DCC SEND transfer
* @dcc: DCC structure containing transfer state
* @transfer_id: ID of the transfer to send data for
*
* Reads a chunk from the source file and sends it over the network socket
* for the specified transfer. Updates transfer state and progress. Handles
* transfer completion when all data is sent.
*
* Return: Number of bytes sent, 0 if complete, -1 on error
*/
int dcc_send(struct dcc *dcc, int transfer_id)
{
if ((dcc == NULL) || (transfer_id < 0) ||
(transfer_id >= KIRC_DCC_TRANSFERS_MAX)) {
return -1;
}
struct dcc_transfer *transfer = &dcc->transfer[transfer_id];
if (transfer->type != DCC_TYPE_SEND) {
printf("\r" CLEAR_LINE DIM "error: %d is not a SEND transfer"
RESET "\r\n", transfer_id);
return -1;
}
if (transfer->state != DCC_STATE_TRANSFERRING) {
printf("\r" CLEAR_LINE DIM "error: %d is not in the transferring state"
RESET "\r\n", transfer_id);
return -1;
}
char buffer[KIRC_DCC_BUFFER_SIZE];
ssize_t nread = read(transfer->file_fd, buffer,
sizeof(buffer));
if (nread < 0) {
printf("\r" CLEAR_LINE DIM "error: file read failed"
RESET "\r\n");
transfer->state = DCC_STATE_ERROR;
return -1;
}
if (nread == 0) {
printf("\r" CLEAR_LINE DIM "dcc: %d transfer complete"
RESET "\r\n", transfer_id);
transfer->state = DCC_STATE_COMPLETE;
return 0;
}
ssize_t nsent = write(dcc->sock_fd[transfer_id].fd,
buffer, nread);
if (nsent < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
return 0;
}
printf("\r" CLEAR_LINE DIM "error: send failed"
RESET "\r\n");
transfer->state = DCC_STATE_ERROR;
return -1;
}
transfer->sent += nsent;
if (transfer->sent >= transfer->filesize) {
printf("\r" CLEAR_LINE DIM "dcc: %d transfer complete"
RESET "\r\n", transfer_id);
transfer->state = DCC_STATE_COMPLETE;
}
return nsent;
}
/**
* dcc_process() - Process pending DCC transfers
* @dcc: DCC structure containing active transfers
*
* Polls all active DCC transfer sockets for I/O readiness and processes
* data transfer, connection establishment, and error handling. Should be
* called periodically in the main event loop. Handles both SEND and
* RECEIVE transfers, cleaning up completed or failed transfers.
*
* Return: 0 on success, -1 on error
*/
int dcc_process(struct dcc *dcc)
{
if (dcc == NULL) {
return -1;
}
if (dcc->transfer_count == 0) {
return 0;
}
int limit = KIRC_DCC_TRANSFERS_MAX;
int rc = poll(dcc->sock_fd, limit, 0);
if (rc < 0) {
if (errno == EINTR) {
return 0;
}
printf("\r" CLEAR_LINE DIM "error: poll failed"
RESET "\r\n");
return -1;
}
if (rc == 0) {
return 0;
}
/* process each transfer */
for (int i = 0; i < limit; ++i) {
if (dcc->sock_fd[i].fd < 0) {
continue;
}
struct dcc_transfer *transfer = &dcc->transfer[i];
if (transfer->state == DCC_STATE_CONNECTING) {
if (dcc->sock_fd[i].revents & POLLOUT) {
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(dcc->sock_fd[i].fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
if (error == 0) {
printf("\r" CLEAR_LINE DIM "dcc: %d connected"
RESET "\r\n", i);
transfer->state = DCC_STATE_TRANSFERRING;
dcc->sock_fd[i].events = POLLIN;
} else {
printf("\r" CLEAR_LINE DIM "error: connection failed"
RESET "\r\n");
transfer->state = DCC_STATE_ERROR;
}
}
}
continue;
}
/* handle receive transfers */
if ((transfer->type == DCC_TYPE_RECEIVE) &&
(transfer->state == DCC_STATE_TRANSFERRING)) {
if (dcc->sock_fd[i].revents & POLLIN) {
char buffer[KIRC_DCC_BUFFER_SIZE];
ssize_t nread = read(dcc->sock_fd[i].fd, buffer,
sizeof(buffer));
if (nread < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
continue;
}
printf("\r" CLEAR_LINE DIM "error: receive failed"
RESET "\r\n");
transfer->state = DCC_STATE_ERROR;
continue;
}
if (nread == 0) {
if (transfer->sent >= transfer->filesize) {
printf("\r" CLEAR_LINE DIM "dcc: %d transfer complete (%llu bytes)"
RESET "\r\n", i, transfer->sent);
transfer->state = DCC_STATE_COMPLETE;
} else {
printf("\r" CLEAR_LINE DIM "error: %d transfer incomplete (%llu/%llu bytes)"
RESET "\r\n", i, transfer->sent, transfer->filesize);
transfer->state = DCC_STATE_COMPLETE;
}
continue;
}
ssize_t nwritten = write(transfer->file_fd, buffer, nread);
if (nwritten < 0) {
printf("\r" CLEAR_LINE DIM "error: write failed"
RESET "\r\n");
transfer->state = DCC_STATE_ERROR;
continue;
}
transfer->sent += nwritten;
if (transfer->sent >= transfer->filesize) {
printf("\r" CLEAR_LINE DIM "dcc: %d transfer complete (%llu bytes)"
RESET "\r\n", i, transfer->sent);
transfer->state = DCC_STATE_COMPLETE;
}
}
}
/* handle send transfer */
if ((transfer->type == DCC_TYPE_SEND) &&
(transfer->state == DCC_STATE_TRANSFERRING)) {
if (dcc->sock_fd[i].revents & POLLOUT) {
dcc_send(dcc, i);
}
}
/* cleanup completed or error transfers */
if ((transfer->state == DCC_STATE_COMPLETE) ||
(transfer->state == DCC_STATE_ERROR)) {
if (dcc->sock_fd[i].fd >= 0) {
close(dcc->sock_fd[i].fd);
dcc->sock_fd[i].fd = -1;
}
if (dcc->transfer[i].file_fd >= 0) {
close(dcc->transfer[i].file_fd);
dcc->transfer[i].file_fd = -1;
}
transfer->state = DCC_STATE_IDLE;
dcc->transfer_count--;
}
}
return 0;
}
/**
* dcc_request() - Handle incoming DCC SEND request
* @dcc: DCC structure to register the transfer
* @sender: Nickname of the user initiating the transfer
* @params: DCC SEND parameters (filename, IP, port, filesize)
*
* Parses a DCC SEND request and initiates a file receive transfer. Creates
* the destination file, establishes a network connection to the sender, and
* registers the transfer for processing. Handles quoted filenames and
* validates parameters for security.
*
* Return: Transfer ID on success, -1 on error
*/
int dcc_request(struct dcc *dcc, const char *sender, const char *params)
{
if ((dcc == NULL) || (sender == NULL) || (params == NULL)) {
return -1;
}
/* find free transfer slot */
int transfer_id = -1;
int limit = KIRC_DCC_TRANSFERS_MAX;
for (int i = 0; i < limit; ++i) {
if (dcc->transfer[i].state == DCC_STATE_IDLE) {
transfer_id = i;
break;
}
}
if (transfer_id < 0) {
printf("\r" CLEAR_LINE DIM "error: no free DCC transfer slots"
RESET "\r\n");
return -1;
}
/* parse DCC SEND parameters: SEND filename ip port filesize */
char params_copy[MESSAGE_MAX_LEN];
size_t siz = sizeof(params_copy);
safecpy(params_copy, params, siz);
char *command = strtok(params_copy, " ");
if ((command == NULL) || (strcmp(command, "SEND") != 0)) {
printf("\r" CLEAR_LINE DIM "error: unsupported DCC command"
RESET "\r\n");
return -1;
}
char *filename_tok = strtok(NULL, " ");
if (filename_tok == NULL) {
printf("\r" CLEAR_LINE DIM "error: invalid DCC SEND format"
RESET "\r\n");
return -1;
}
/* handle quoted filenames */
char filename[NAME_MAX];
if (filename_tok[0] == '"') {
/* find closing quote */
char *end_quote = strchr(filename_tok + 1, '"');
if (end_quote != NULL) {
size_t len = end_quote - filename_tok - 1;
if (len >= NAME_MAX) {
len = NAME_MAX;
} else {
len = len + 1;
}
safecpy(filename, filename_tok + 1, len);
} else {
/* filename spans multiple tokens */
size_t len = strlen(filename_tok + 1);
if (len >= NAME_MAX) {
len = NAME_MAX;
} else {
len = len + 1;
}
safecpy(filename, filename_tok + 1, len);
/* continue reading until closing quote */
char *next = strtok(NULL, "\"");
if (next != NULL) {
size_t flen = strlen(filename);
size_t nlen = strlen(next);
if (flen + nlen + 1 < NAME_MAX) {
filename[flen] = ' ';
strncpy(filename + flen + 1, next, NAME_MAX - flen - 2);
filename[NAME_MAX - 1] = '\0';
}
}
}
} else {
siz = sizeof(filename);
safecpy(filename, filename_tok, siz);
}
if (sanitize_filename(filename) < 0) {
printf("\r" CLEAR_LINE DIM "error: invalid or unsafe filename"
RESET "\r\n");
return -1;
}
char *server = strtok(NULL, " ");
char *port = strtok(NULL, " ");
char *filesize = strtok(NULL, " ");
if ((server == NULL) || (port == NULL) || (filesize == NULL)) {
printf("\r" CLEAR_LINE DIM "error: invalid DCC SEND format"
RESET "\r\n");
return -1;
}
/* initialize transfer */
struct dcc_transfer *transfer = &dcc->transfer[transfer_id];
transfer->type = DCC_TYPE_RECEIVE;
transfer->state = DCC_STATE_CONNECTING;
transfer->filesize = strtoull(filesize, NULL, 10);
transfer->sent = 0;
siz = sizeof(transfer->filename);
safecpy(transfer->filename, filename, siz);
siz = sizeof(transfer->sender);
safecpy(transfer->sender, sender, siz);
/* open file for writing */
transfer->file_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (transfer->file_fd < 0) {
printf("\r" CLEAR_LINE DIM "error: cannot create file %s"
RESET "\r\n", filename);
transfer->state = DCC_STATE_IDLE;
return -1;
}
/* create socket */
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int rc = getaddrinfo(server, port, &hints, &res);
if (rc != 0) {
printf("\r" CLEAR_LINE DIM "error: getaddrinfo failed: %s"
RESET "\r\n", gai_strerror(rc));
close(transfer->file_fd);
transfer->file_fd = -1;
transfer->state = DCC_STATE_IDLE;
return -1;
}
int sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock_fd < 0) {
printf("\r" CLEAR_LINE DIM "error: socket creation failed"
RESET "\r\n");
freeaddrinfo(res);
close(transfer->file_fd);
transfer->file_fd = -1;
transfer->state = DCC_STATE_IDLE;
return -1;
}
/* set non-blocking */
int flags = fcntl(sock_fd, F_GETFL, 0);
fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
/* connect */
rc = connect(sock_fd, res->ai_addr, res->ai_addrlen);
if ((rc < 0) && (errno != EINPROGRESS)) {
printf("\r" CLEAR_LINE DIM "error: connection failed"
RESET "\r\n");
close(sock_fd);
freeaddrinfo(res);
close(transfer->file_fd);
transfer->file_fd = -1;
transfer->state = DCC_STATE_IDLE;
return -1;
}
freeaddrinfo(res);
dcc->sock_fd[transfer_id].fd = sock_fd;
dcc->sock_fd[transfer_id].events = POLLOUT;
dcc->transfer_count++;
printf("\r" CLEAR_LINE DIM "dcc: receiving %s from %s (%llu bytes)"
RESET "\r\n", transfer->filename, transfer->sender,
transfer->filesize);
return transfer_id;
}
/**
* dcc_cancel() - Cancel an active DCC transfer
* @dcc: DCC structure containing the transfer
* @transfer_id: ID of the transfer to cancel
*
* Cancels a DCC transfer by closing associated socket and file descriptors
* and resetting the transfer state to idle. Decrements the active transfer
* count.
*
* Return: 0 on success, -1 if parameters are invalid
*/
int dcc_cancel(struct dcc *dcc, int transfer_id)
{
if ((dcc == NULL) || (transfer_id < 0) ||
(transfer_id >= KIRC_DCC_TRANSFERS_MAX)) {
return -1;
}
struct dcc_transfer *transfer = &dcc->transfer[transfer_id];
if (transfer->state == DCC_STATE_IDLE) {
return 0;
}
printf("\r" CLEAR_LINE DIM "dcc: cancelling transfer %d"
RESET "\r\n", transfer_id);
if (dcc->sock_fd[transfer_id].fd >= 0) {
close(dcc->sock_fd[transfer_id].fd);
dcc->sock_fd[transfer_id].fd = -1;
}
if (dcc->transfer[transfer_id].file_fd >= 0) {
close(dcc->transfer[transfer_id].file_fd);
dcc->transfer[transfer_id].file_fd = -1;
}
transfer->state = DCC_STATE_IDLE;
dcc->transfer_count--;
return 0;
}
/**
* dcc_handle() - Handle DCC-related IRC events
* @dcc: DCC structure for managing transfers
* @network: Network connection structure
* @event: Event structure containing DCC command
*
* Processes DCC events from IRC messages. Currently handles DCC SEND
* requests by calling dcc_request() when a CTCP DCC PRIVMSG is received.
*/
void dcc_handle(struct dcc *dcc, struct network *network, struct event *event)
{
(void)network;
if (dcc == NULL || event == NULL) {
return;
}
if (event->type != EVENT_CTCP_DCC) {
return;
}
if (strcmp(event->command, "PRIVMSG") == 0) {
dcc_request(dcc, event->nickname, event->message);
}
}
================================================
FILE: src/editor.c
================================================
/*
* editor.c
* Text editor functionality for the IRC client
* Author: Michael Czigler
* License: MIT
*/
#include "editor.h"
/**
* editor_backspace() - Delete the character before the cursor
* @editor: Editor state structure
*
* Removes the UTF-8 character immediately before the cursor position,
* properly handling multi-byte UTF-8 sequences. Does nothing if cursor
* is at the beginning of the line.
*/
static void editor_backspace(struct editor *editor)
{
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
if (editor->cursor <= 0) {
return; /* nothing to delete or out of range */
}
/* Get length of previous UTF-8 character */
int bytes = utf8_prev_char_len(editor->scratch, editor->cursor);
if (bytes == 0) {
return;
}
editor->cursor -= bytes;
memmove(editor->scratch + editor->cursor,
editor->scratch + editor->cursor + bytes,
len - (editor->cursor + bytes) + 1);
}
/**
* editor_delete_line() - Clear the entire input line
* @editor: Editor state structure
*
* Erases all text from the scratch buffer and resets the cursor to the
* beginning of the line.
*/
static void editor_delete_line(struct editor *editor)
{
editor->scratch[0] = '\0';
editor->cursor = 0;
}
/**
* editor_enter() - Process enter key to submit current line
* @editor: Editor state structure
*
* Saves the current line to history, advances the history pointer, and
* clears the scratch buffer. Does nothing if the line is empty.
*
* Return: 1 if line was submitted, 0 if line was empty
*/
static int editor_enter(struct editor *editor)
{
if (editor->scratch[0] == '\0') {
return 0; /* nothing to send */
}
int siz = sizeof(editor->scratch) - 1;
strncpy(editor->history[editor->head],
editor->scratch, siz);
editor->head = (editor->head + 1) % KIRC_HISTORY_SIZE;
if (editor->count < KIRC_HISTORY_SIZE) {
editor->count++;
}
editor->position = -1;
editor_delete_line(editor);
return 1;
}
/**
* editor_delete() - Delete the character at the cursor
* @editor: Editor state structure
*
* Removes the UTF-8 character at the current cursor position, properly
* handling multi-byte UTF-8 sequences. Does nothing if cursor is at the
* end of the line.
*/
static void editor_delete(struct editor *editor)
{
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
if (editor->cursor >= len) {
return; /* at end of scratch string */
}
/* Get length of next UTF-8 character using mbrtowc */
int bytes = utf8_next_char_len(editor->scratch, editor->cursor, len);
if (bytes == 0) {
return;
}
memmove(editor->scratch + editor->cursor,
editor->scratch + editor->cursor + bytes,
len - (editor->cursor + bytes) + 1);
}
/**
* editor_history() - Navigate through command history
* @editor: Editor state structure
* @dir: Direction to navigate (positive for previous, negative for next)
*
* Allows browsing through the command history using up/down arrows. Loads
* the previous or next history entry into the scratch buffer and updates
* the cursor position. Handles wraparound at history boundaries.
*/
static void editor_history(struct editor *editor, int dir)
{
int siz = KIRC_HISTORY_SIZE;
if (editor->count == 0) {
return;
}
int head = editor->head;
int oldest = (head - editor->count + siz) % siz;
int newest = (head - 1 + siz) % siz;
if (dir > 0) { /* up or previous */
int pos;
if (editor->position == -1) {
pos = newest;
} else if (editor->position == oldest) {
return; /* already at oldest */
} else {
pos = (editor->position - 1 + siz) % siz;
}
strncpy(editor->scratch, editor->history[pos],
sizeof(editor->scratch) - 1);
editor->scratch[sizeof(editor->scratch) - 1] = '\0';
editor->cursor = strnlen(editor->scratch,
sizeof(editor->scratch) - 1);
editor->position = pos;
} else { /* down or next */
if (editor->position == -1) {
return; /* not browsing */
}
if (editor->position == newest) {
editor_delete_line(editor);
editor->position = -1;
return;
}
int pos = (editor->position + 1) % siz;
strncpy(editor->scratch, editor->history[pos],
sizeof(editor->scratch) - 1);
editor->scratch[sizeof(editor->scratch) - 1] = '\0';
editor->cursor = strnlen(editor->scratch,
sizeof(editor->scratch) - 1);
editor->position = pos;
}
}
/**
* editor_move_right() - Move cursor one character to the right
* @editor: Editor state structure
*
* Advances the cursor by one UTF-8 character, properly handling multi-byte
* sequences. Does nothing if cursor is at the end of the line.
*/
static void editor_move_right(struct editor *editor)
{
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
if (editor->cursor >= len) {
return; /* at end */
}
/* Use mbrtowc to get proper character length */
int adv = utf8_next_char_len(editor->scratch, editor->cursor, len);
if (adv > 0 && editor->cursor + adv <= len) {
editor->cursor += adv;
} else {
/* Invalid sequence or truncated, move to end */
editor->cursor = len;
}
}
/**
* editor_move_left() - Move cursor one character to the left
* @editor: Editor state structure
*
* Moves the cursor back by one UTF-8 character, properly handling multi-byte
* sequences. Does nothing if cursor is at the beginning of the line.
*/
static void editor_move_left(struct editor *editor)
{
if (editor->cursor <= 0) {
return;
}
/* Get length of previous UTF-8 character */
int bytes = utf8_prev_char_len(editor->scratch, editor->cursor);
if (bytes > 0) {
editor->cursor -= bytes;
}
}
/**
* editor_move_home() - Move cursor to beginning of line
* @editor: Editor state structure
*
* Sets the cursor position to the start of the input line.
*/
static void editor_move_home(struct editor *editor)
{
editor->cursor = 0;
}
/**
* editor_move_end() - Move cursor to end of line
* @editor: Editor state structure
*
* Sets the cursor position to the end of the current input line.
*/
static void editor_move_end(struct editor *editor)
{
size_t siz = sizeof(editor->scratch) - 1;
editor->cursor = strnlen(editor->scratch, siz);
}
/**
* editor_escape() - Handle escape sequences for special keys
* @editor: Editor state structure
*
* Processes ANSI escape sequences for cursor movement and special keys
* (arrow keys, Home, End, Delete). Reads additional bytes from stdin
* to determine the complete escape sequence and executes the appropriate
* editor action.
*/
static void editor_escape(struct editor *editor)
{
char seq[3];
if (read(STDIN_FILENO, &seq[0], 1) != 1) {
return;
}
if (read(STDIN_FILENO, &seq[1], 1) != 1) {
return;
}
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
return;
}
if (seq[1] == '3' && seq[2] == '~') {
editor_delete(editor);
}
} else {
switch (seq[1]) {
case 'A':
editor_history(editor, 1);
break;
case 'B':
editor_history(editor, -1);
break;
case 'C':
editor_move_right(editor);
break;
case 'D':
editor_move_left(editor);
break;
case 'H':
editor_move_home(editor);
break;
case 'F':
editor_move_end(editor);
break;
}
}
}
}
/**
* editor_insert() - Insert a single character at cursor position
* @editor: Editor state structure
* @c: Character to insert
*
* Inserts a single byte character at the current cursor position, shifting
* existing text to the right. Advances the cursor after insertion. Does
* nothing if the buffer is full.
*/
static void editor_insert(struct editor *editor, char c)
{
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
if (editor->cursor > siz) {
return; /* at end of scratch */
}
if (len + 1 >= siz) {
return; /* scratch full */
}
memmove(editor->scratch + editor->cursor + 1,
editor->scratch + editor->cursor,
len - editor->cursor + 1);
editor->scratch[editor->cursor] = c;
editor->cursor++;
}
/**
* editor_insert_bytes() - Insert multiple bytes at cursor position
* @editor: Editor state structure
* @buf: Buffer containing bytes to insert
* @n: Number of bytes to insert
*
* Inserts a sequence of bytes (typically a multi-byte UTF-8 character) at
* the current cursor position. Validates UTF-8 encoding before insertion.
* Shifts existing text to the right and advances the cursor.
*/
static void editor_insert_bytes(struct editor *editor, const char *buf, int n)
{
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
if (editor->cursor > siz) {
return; /* at end of scratch */
}
if (len + n >= siz) {
return; /* scratch full */
}
/* Validate UTF-8 sequence before inserting */
if (!utf8_validate(buf, n)) {
return; /* invalid UTF-8, reject */
}
memmove(editor->scratch + editor->cursor + n,
editor->scratch + editor->cursor,
len - editor->cursor + 1);
memcpy(editor->scratch + editor->cursor, buf, n);
editor->cursor += n;
}
/**
* editor_clear() - Clear the current line on terminal
* @editor: Editor state structure (unused)
*
* Sends ANSI escape codes to clear the current terminal line, moving
* the cursor to the beginning.
*/
static void editor_clear(struct editor *editor)
{
printf("\r" CLEAR_LINE);
}
/**
* display_width_bytes() - Calculate display width of UTF-8 string
* @s: UTF-8 encoded string
* @bytes: Number of bytes to measure
*
* Computes the display width (number of terminal columns) occupied by
* a UTF-8 string, accounting for wide characters (e.g., CJK characters).
* Handles invalid sequences by treating them as width 1.
*
* Return: Display width in terminal columns
*/
static int display_width_bytes(const char *s, int bytes)
{
mbstate_t st;
memset(&st, 0, sizeof(st));
int pos = 0;
int wsum = 0;
while (pos < bytes) {
wchar_t wc;
size_t ret = mbrtowc(&wc, s + pos, bytes - pos, &st);
if (ret == (size_t)-1 || ret == (size_t)-2) {
/* invalid sequence: treat as width 1 */
pos++;
wsum += 1;
memset(&st, 0, sizeof(st));
continue;
}
if (ret == 0) {
pos++;
continue;
}
int w = wcwidth(wc);
if (w < 0) w = 0;
wsum += w;
pos += ret;
}
return wsum;
}
/**
* editor_tab() - Insert tab as spaces
* @editor: Editor state structure
*
* Inserts KIRC_TAB_WIDTH spaces at the cursor position to simulate
* a tab character.
*/
static void editor_tab(struct editor *editor)
{
int width = KIRC_TAB_WIDTH;
for (int i = 0; i < width; ++i) {
editor_insert(editor, ' ');
}
}
/**
* editor_last_entry() - Get the most recent history entry
* @editor: Editor state structure
*
* Returns a pointer to the last command entered in the history buffer.
*
* Return: Pointer to the most recent history string
*/
char *editor_last_entry(struct editor *editor)
{
int head = editor->head;
int last = (head - 1 + KIRC_HISTORY_SIZE) % KIRC_HISTORY_SIZE;
return editor->history[last];
}
/**
* editor_init() - Initialize the editor state
* @editor: Editor structure to initialize
* @ctx: IRC context structure
*
* Initializes the editor with zeroed state, sets up locale for UTF-8
* support, and associates it with the IRC context. Prepares the editor
* for input processing.
*
* Return: 0 on success, -1 if editor or ctx is NULL
*/
int editor_init(struct editor *editor, struct kirc_context *ctx)
{
if ((editor == NULL) || (ctx == NULL)) {
return -1;
}
memset(editor, 0, sizeof(*editor));
editor->ctx = ctx;
editor->state = EDITOR_STATE_NONE;
editor->position = -1;
setlocale(LC_CTYPE, "");
return 0;
}
/**
* editor_process_key() - Process a single keystroke
* @editor: Editor state structure
*
* Reads and processes one character from stdin. Handles control characters,
* escape sequences, and UTF-8 multi-byte input. Updates editor state
* (SEND, TERMINATE, or NONE) based on the key pressed.
*
* Return: 0 on success, 1 if read failed
*/
int editor_process_key(struct editor *editor)
{
char c;
if (read(STDIN_FILENO, &c, 1) < 1) {
return 1;
}
editor->state = EDITOR_STATE_NONE;
switch(c) {
case HT: /* CTRL-I or TAB */
editor_tab(editor);
break;
case ETX: /* CTRL-C */
editor_clear(editor);
editor->state = EDITOR_STATE_TERMINATE;
break;
case NAK: /* CTRL-U */
editor_delete_line(editor);
break;
case BS: /* CTRL-H */
case DEL:
editor_backspace(editor);
break;
case CR: /* CTRL-M or ENTER */
if (editor_enter(editor) > 0) {
editor->state = EDITOR_STATE_SEND;
}
break;
case ESC: /* CTRL-[ */
editor_escape(editor);
break;
case SOH: /* CTRL-A */
case STX: /* CTRL-B */
case EOT: /* CTRL-D */
case ENQ: /* CTRL-E */
case ACK: /* CTRL-F */
case BEL: /* CTRL-G */
case LF: /* CTRL-J */
case VT: /* CTRL-K */
case FF: /* CTRL-L */
case SO: /* CTRL-N */
case SI: /* CTRL-O */
case DLE: /* CTRL-P */
case DC1: /* CTRL-Q */
case DC2: /* CTRL-R */
case DC3: /* CTRL-S */
case DC4: /* CTRL-T */
case SYN: /* CTRL-V */
case ETB: /* CTRL-W */
case CAN: /* CTRL-X */
case EM: /* CTRL-Y */
case SUB: /* CTRL-Z */
case FS: /* CTRL-\ */
case GS: /* CTRL-] */
case RS: /* CTRL-^ */
case US: /* CTRL-_ */
break; /* not implemented yet */
default:
/* handle UTF-8 multi-byte input: read remaining continuation bytes */
{
unsigned char uc = (unsigned char)c;
char buf[5];
int need = 0;
buf[0] = c;
if ((uc & 0x80) == 0) {
editor_insert(editor, c);
} else {
if ((uc & 0xE0) == 0xC0) need = 1;
else if ((uc & 0xF0) == 0xE0) need = 2;
else if ((uc & 0xF8) == 0xF0) need = 3;
else need = 0;
int i;
for (i = 1; i <= need; i++) {
if (read(STDIN_FILENO, &buf[i], 1) != 1) {
break;
}
}
editor_insert_bytes(editor, buf, 1 + (i - 1));
}
}
break;
}
return 0;
}
/**
* editor_handle() - Render the editor display
* @editor: Editor state structure
*
* Renders the current editor state to the terminal, displaying the target
* channel/user and the input text. Handles line scrolling when text exceeds
* terminal width and positions the cursor correctly. Accounts for UTF-8
* character widths for proper display alignment.
*
* Return: 0 on success
*/
int editor_handle(struct editor *editor)
{
int cols = terminal_columns(STDIN_FILENO);
int size = strlen(editor->ctx->target) + 1;
int avail = cols - size - 1;
int siz = sizeof(editor->scratch) - 1;
int len = strnlen(editor->scratch, siz);
/* compute display width of bytes up to cursor */
int cursor_disp = display_width_bytes(editor->scratch, editor->cursor);
/* choose start byte offset so that the substring ending at cursor fits in avail */
int start = editor->cursor;
int used = 0;
while (start > 0) {
int char_bytes = utf8_prev_char_len(editor->scratch, start);
if (char_bytes == 0) break;
int cw = display_width_bytes(editor->scratch + start - char_bytes, char_bytes);
if (used + cw > avail) break;
used += cw;
start -= char_bytes;
}
/* compute how many bytes we can print from start given avail */
int bytes_to_print = 0;
int p = start;
int printed_width = 0;
while (p < len) {
int cb = utf8_next_char_len(editor->scratch, p, len);
if (cb == 0) break;
int cw = display_width_bytes(editor->scratch + p, cb);
if (printed_width + cw > avail) break;
printed_width += cw;
p += cb;
}
bytes_to_print = p - start;
printf("\r%s:", editor->ctx->target);
if (bytes_to_print > 0) {
fwrite(editor->scratch + start, 1, bytes_to_print, stdout);
}
printf(" " CLEAR_LINE);
printf("\r\x1b[%dC", cursor_disp + size);
fflush(stdout);
return 0;
}
================================================
FILE: src/event.c
================================================
/*
* event.c
* Message event handling
* Author: Michael Czigler
* License: MIT
*/
#include "event.h"
/* Event dispatch table - used only for parsing */
static const struct event_dispatch_table event_table[] = {
{ "CAP", EVENT_EXT_CAP },
{ "JOIN", EVENT_JOIN },
{ "KICK", EVENT_KICK },
{ "MODE", EVENT_MODE },
{ "NICK", EVENT_NICK },
{ "NOTICE", EVENT_NOTICE },
{ "PART", EVENT_PART },
{ "PING", EVENT_PING },
{ "PRIVMSG", EVENT_PRIVMSG },
{ "QUIT", EVENT_QUIT },
{ "TOPIC", EVENT_TOPIC },
{ "001", EVENT_001_RPL_WELCOME },
{ "002", EVENT_002_RPL_YOURHOST },
{ "003", EVENT_003_RPL_CREATED },
{ "004", EVENT_004_RPL_MYINFO },
{ "005", EVENT_005_RPL_BOUNCE },
{ "042", EVENT_042_RPL_YOURID },
{ "200", EVENT_200_RPL_TRACELINK },
{ "201", EVENT_201_RPL_TRACECONNECTING },
{ "202", EVENT_202_RPL_TRACEHANDSHAKE },
{ "203", EVENT_203_RPL_TRACEUNKNOWN },
{ "204", EVENT_204_RPL_TRACEOPERATOR },
{ "205", EVENT_205_RPL_TRACEUSER },
{ "206", EVENT_206_RPL_TRACESERVER },
{ "207", EVENT_207_RPL_TRACESERVICE },
{ "208", EVENT_208_RPL_TRACENEWTYPE },
{ "209", EVENT_209_RPL_TRACECLASS },
{ "211", EVENT_211_RPL_STATSLINKINFO },
{ "212", EVENT_212_RPL_STATSCOMMANDS },
{ "213", EVENT_213_RPL_STATSCLINE},
{ "215", EVENT_215_RPL_STATSILINE },
{ "216", EVENT_216_RPL_STATSKLINE },
{ "218", EVENT_218_RPL_STATSYLINE },
{ "219", EVENT_219_RPL_ENDOFSTATS },
{ "221", EVENT_221_RPL_UMODEIS },
{ "234", EVENT_234_RPL_SERVLIST },
{ "235", EVENT_235_RPL_SERVLISTEND },
{ "241", EVENT_241_RPL_STATSLLINE },
{ "242", EVENT_242_RPL_STATSUPTIME },
{ "243", EVENT_243_RPL_STATSOLINE },
{ "244", EVENT_244_RPL_STATSHLINE },
{ "245", EVENT_245_RPL_STATSSLINE },
{ "250", EVENT_250_RPL_STATSCONN },
{ "251", EVENT_251_RPL_LUSERCLIENT },
{ "252", EVENT_252_RPL_LUSEROP },
{ "253", EVENT_253_RPL_LUSERUNKNOWN },
{ "254", EVENT_254_RPL_LUSERCHANNELS },
{ "255", EVENT_255_RPL_LUSERME },
{ "256", EVENT_256_RPL_ADMINME },
{ "257", EVENT_257_RPL_ADMINLOC1 },
{ "258", EVENT_258_RPL_ADMINLOC2 },
{ "259", EVENT_259_RPL_ADMINEMAIL },
{ "261", EVENT_261_RPL_TRACELOG },
{ "263", EVENT_263_RPL_TRYAGAIN },
{ "265", EVENT_265_RPL_LOCALUSERS },
{ "266", EVENT_266_RPL_GLOBALUSERS },
{ "300", EVENT_300_RPL_NONE },
{ "301", EVENT_301_RPL_AWAY },
{ "302", EVENT_302_RPL_USERHOST },
{ "303", EVENT_303_RPL_ISON },
{ "305", EVENT_305_RPL_UNAWAY },
{ "306", EVENT_306_RPL_NOWAWAY },
{ "311", EVENT_311_RPL_WHOISUSER },
{ "312", EVENT_312_RPL_WHOISSERVER },
{ "313", EVENT_313_RPL_WHOISOPERATOR },
{ "314", EVENT_314_RPL_WHOWASUSER },
{ "315", EVENT_315_RPL_ENDOFWHO },
{ "317", EVENT_317_RPL_WHOISIDLE },
{ "318", EVENT_318_RPL_ENDOFWHOIS },
{ "319", EVENT_319_RPL_WHOISCHANNELS },
{ "322", EVENT_322_RPL_LIST },
{ "323", EVENT_323_RPL_LISTEND },
{ "324", EVENT_324_RPL_CHANNELMODEIS },
{ "328", EVENT_328_RPL_CHANNEL_URL },
{ "331", EVENT_331_RPL_NOTOPIC },
{ "332", EVENT_332_RPL_TOPIC },
{ "333", EVENT_333_RPL_TOPICWHOTIME },
{ "341", EVENT_341_RPL_INVITING },
{ "346", EVENT_346_RPL_INVITELIST },
{ "347", EVENT_347_RPL_ENDOFINVITELIST },
{ "348", EVENT_348_RPL_EXCEPTLIST },
{ "349", EVENT_349_RPL_ENDOFEXCEPTLIST },
{ "351", EVENT_351_RPL_VERSION },
{ "352", EVENT_352_RPL_WHOREPLY },
{ "353", EVENT_353_RPL_NAMREPLY },
{ "364", EVENT_364_RPL_LINKS },
{ "365", EVENT_365_RPL_ENDOFLINKS },
{ "366", EVENT_366_RPL_ENDOFNAMES },
{ "367", EVENT_367_RPL_BANLIST },
{ "368", EVENT_368_RPL_ENDOFBANLIST },
{ "369", EVENT_369_RPL_ENDOFWHOWAS },
{ "375", EVENT_375_RPL_MOTDSTART },
{ "371", EVENT_371_RPL_INFO },
{ "372", EVENT_372_RPL_MOTD },
{ "374", EVENT_374_RPL_ENDOFINFO },
{ "376", EVENT_376_RPL_ENDOFMOTD },
{ "381", EVENT_381_RPL_YOUREOPER },
{ "382", EVENT_382_RPL_REHASHING },
{ "383", EVENT_383_RPL_YOURESERVICE },
{ "391", EVENT_391_RPL_TIME },
{ "392", EVENT_392_RPL_USERSSTART },
{ "393", EVENT_393_RPL_USERS },
{ "394", EVENT_394_RPL_ENDOFUSERS },
{ "395", EVENT_395_RPL_NOUSERS },
{ "396", EVENT_396_RPL_HOSTHIDDEN },
{ "400", EVENT_400_ERR_UNKNOWNERROR },
{ "401", EVENT_401_ERR_NOSUCHNICK },
{ "402", EVENT_402_ERR_NOSUCHSERVER },
{ "403", EVENT_403_ERR_NOSUCHCHANNEL },
{ "404", EVENT_404_ERR_CANNOTSENDTOCHAN },
{ "405", EVENT_405_ERR_TOOMANYCHANNELS },
{ "406", EVENT_406_ERR_WASNOSUCHNICK },
{ "407", EVENT_407_ERR_TOOMANYTARGETS },
{ "408", EVENT_408_ERR_NOSUCHSERVICE },
{ "409", EVENT_409_ERR_NOORIGIN },
{ "411", EVENT_411_ERR_NORECIPIENT },
{ "412", EVENT_412_ERR_NOTEXTTOSEND },
{ "413", EVENT_413_ERR_NOTOPLEVEL },
{ "414", EVENT_414_ERR_WILDTOPLEVEL },
{ "415", EVENT_415_ERR_BADMASK },
{ "421", EVENT_421_ERR_UNKNOWNCOMMAND },
{ "422", EVENT_422_ERR_NOMOTD },
{ "423", EVENT_423_ERR_NOADMININFO },
{ "424", EVENT_424_ERR_FILEERROR },
{ "431", EVENT_431_ERR_NONICKNAMEGIVEN },
{ "432", EVENT_432_ERR_ERRONEUSNICKNAME },
{ "433", EVENT_433_ERR_NICKNAMEINUSE },
{ "436", EVENT_436_ERR_NICKCOLLISION },
{ "441", EVENT_441_ERR_USERNOTINCHANNEL },
{ "442", EVENT_442_ERR_NOTONCHANNEL },
{ "443", EVENT_443_ERR_USERONCHANNEL },
{ "444", EVENT_444_ERR_NOLOGIN },
{ "445", EVENT_445_ERR_SUMMONDISABLED },
{ "446", EVENT_446_ERR_USERSDISABLED },
{ "451", EVENT_451_ERR_NOTREGISTERED },
{ "461", EVENT_461_ERR_NEEDMOREPARAMS },
{ "462", EVENT_462_ERR_ALREADYREGISTERED },
{ "463", EVENT_463_ERR_NOPERMFORHOST },
{ "464", EVENT_464_ERR_PASSWDMISMATCH },
{ "465", EVENT_465_ERR_YOUREBANNEDCREEP },
{ "467", EVENT_467_ERR_KEYSET },
{ "470", EVENT_470_ERR_LINKCHANNEL },
{ "471", EVENT_471_ERR_CHANNELISFULL },
{ "472", EVENT_472_ERR_UNKNOWNMODE },
{ "473", EVENT_473_ERR_INVITEONLYCHAN },
{ "474", EVENT_474_ERR_BANNEDFROMCHAN },
{ "475", EVENT_475_ERR_BADCHANNELKEY },
{ "476", EVENT_476_ERR_BADCHANMASK },
{ "477", EVENT_477_ERR_NEEDREGGEDNICK },
{ "478", EVENT_478_ERR_BANLISTFULL },
{ "481", EVENT_481_ERR_NOPRIVILEGES },
{ "482", EVENT_482_ERR_CHANOPRIVSNEEDED },
{ "483", EVENT_483_ERR_CANTKILLSERVER },
{ "485", EVENT_485_ERR_UNIQOPRIVSNEEDED },
{ "491", EVENT_491_ERR_NOOPERHOST },
{ "501", EVENT_501_ERR_UMODEUNKNOWNFLAG },
{ "502", EVENT_502_ERR_USERSDONTMATCH },
{ "524", EVENT_524_ERR_HELPNOTFOUND },
{ "704", EVENT_704_RPL_HELPSTART },
{ "705", EVENT_705_RPL_HELPTXT },
{ "706", EVENT_706_RPL_ENDOFHELP },
{ "900", EVENT_900_RPL_LOGGEDIN },
{ "901", EVENT_901_RPL_LOGGEDOUT },
{ "902", EVENT_902_ERR_NICKLOCKED },
{ "903", EVENT_903_RPL_SASLSUCCESS },
{ "904", EVENT_904_ERR_SASLFAIL },
{ "905", EVENT_905_ERR_SASLTOOLONG },
{ "906", EVENT_906_ERR_SASLABORTED },
{ "907", EVENT_907_ERR_SASLALREADY },
{ "908", EVENT_908_RPL_SASLMECHS },
{ NULL, EVENT_NONE }
};
/**
* event_init() - Initialize an event structure
* @event: Event structure to initialize
* @ctx: IRC context structure
*
* Zeros out the event structure and associates it with an IRC context.
* Sets the event type to EVENT_NONE.
*
* Return: 0 on success
*/
int event_init(struct event *event, struct kirc_context *ctx)
{
memset(event, 0, sizeof(*event));
event->ctx = ctx;
event->type = EVENT_NONE;
return 0;
}
/**
* event_ctcp_parse() - Parse CTCP commands from message
* @event: Event structure containing the message to parse
*
* Detects and parses CTCP (Client-To-Client Protocol) commands embedded
* in PRIVMSG or NOTICE events. CTCP commands are delimited by \001 characters.
* Supports ACTION, VERSION, PING, TIME, CLIENTINFO, and DCC commands.
* Updates the event type and extracts command parameters.
*
* Return: 0 on success
*/
static int event_ctcp_parse(struct event *event)
{
if (((event->type == EVENT_PRIVMSG) ||
(event->type == EVENT_NOTICE)) &&
(event->message[0] == '\001')) {
char ctcp[MESSAGE_MAX_LEN];
size_t siz = sizeof(event->message);
size_t len = strnlen(event->message, siz);
size_t end = 1;
while ((end < len) && event->message[end] != '\001') {
end++;
}
size_t ctcp_len = (end > 1) ? end - 1 : 0;
if (ctcp_len > 0) {
size_t copy_n;
if (ctcp_len < sizeof(ctcp)) {
copy_n = ctcp_len + 1;
} else {
copy_n = sizeof(ctcp);
}
safecpy(ctcp, event->message + 1, copy_n);
char *command = strtok(ctcp, " ");
char *args = strtok(NULL, "");
if (command != NULL) {
if (strcmp(command, "ACTION") == 0) {
event->type = EVENT_CTCP_ACTION;
if (args != NULL) {
siz = sizeof(event->message);
safecpy(event->message, args, siz);
} else {
event->message[0] = '\0';
}
} else if (strcmp(command, "VERSION") == 0) {
event->type = EVENT_CTCP_VERSION;
if (args != NULL) {
siz = sizeof(event->params);
safecpy(event->params, args, siz);
} else {
event->params[0] = '\0';
}
} else if (strcmp(command, "PING") == 0) {
event->type = EVENT_CTCP_PING;
if (args != NULL) {
siz = sizeof(event->message);
safecpy(event->message, args, siz);
} else {
event->message[0] = '\0';
}
} else if (strcmp(command, "TIME") == 0) {
event->type = EVENT_CTCP_TIME;
} else if (strcmp(command, "CLIENTINFO") == 0) {
event->type = EVENT_CTCP_CLIENTINFO;
} else if (strcmp(command, "DCC") == 0) {
event->type = EVENT_CTCP_DCC;
if (args != NULL) {
siz = sizeof(event->message);
safecpy(event->message, args, siz);
} else {
event->message[0] = '\0';
}
} else {
event->message[0] = '\0';
}
}
}
}
return 0;
}
/**
* event_parse() - Parse IRC message into event structure
* @event: Event structure to populate
* @line: Raw IRC message line to parse
*
* Parses a raw IRC protocol message into structured event data. Handles
* special cases (PING, AUTHENTICATE, ERROR) and general IRC message format.
* Extracts prefix, command, channel, nickname, and message components.
* Determines event type from command using the event dispatch table.
* Calls event_ctcp_parse() to detect CTCP commands.
*
* Return: 0 on success, -1 if parsing fails
*/
int event_parse(struct event *event, char *line)
{
char line_copy[MESSAGE_MAX_LEN];
safecpy(line_copy, line, sizeof(line_copy));
safecpy(event->raw, line, sizeof(event->raw));
if (strncmp(line, "PING", 4) == 0) {
event->type = EVENT_PING;
size_t message_n = sizeof(event->message);
safecpy(event->message, line + 6, message_n);
return 0;
}
if (strncmp(line, "AUTHENTICATE +", 14) == 0) {
event->type = EVENT_EXT_AUTHENTICATE;
return 0;
}
if (strncmp(line, "ERROR", 5) == 0) {
event->type = EVENT_ERROR;
size_t message_n = sizeof(event->message);
safecpy(event->message, line + 7, message_n);
return 0;
}
char *token = strtok(line_copy, " ");
if (token == NULL) {
return -1;
}
char *prefix = token + 1;
char *suffix = strtok(NULL, ":");
if (suffix == NULL) {
return -1;
}
char *message = strtok(NULL, "\r");
if (message != NULL) {
size_t message_n = sizeof(event->message);
safecpy(event->message, message, message_n);
}
char *nickname = strtok(prefix, "!");
if (nickname != NULL) {
size_t nickname_n = sizeof(event->nickname);
safecpy(event->nickname, nickname, nickname_n);
}
char *command = strtok(suffix, "#& ");
if (command != NULL) {
size_t command_n = sizeof(event->command);
safecpy(event->command, command, command_n);
}
char *channel = strtok(NULL, " \r");
if (channel != NULL) {
size_t channel_n = sizeof(event->channel);
safecpy(event->channel, channel, channel_n);
}
char *params = strtok(NULL, ":\r");
if (params != NULL) {
size_t params_n = sizeof(event->params);
safecpy(event->params, params, params_n);
}
for (int i = 0; event_table[i].command != NULL; i++) {
if (strcmp(event->command, event_table[i].command) == 0) {
event->type = event_table[i].type;
break;
}
}
event_ctcp_parse(event);
return 0;
}
================================================
FILE: src/handler.c
================================================
/*
* handler.c
* Unified event handler dispatch system
* Author: Michael Czigler
* License: MIT
*/
#include "handler.h"
/**
* handler_default() - Set the default event handler
* @handler: Handler structure to configure
* @handler_fn: Function to call for unhandled events
*
* Registers a fallback handler that will be called for events that don't
* have a specific handler registered.
*/
void handler_default(struct handler *handler,
event_handler_fn handler_fn)
{
handler->default_handler = handler_fn;
}
/**
* handler_register() - Register a handler for an event type
* @handler: Handler structure to configure
* @type: Event type to register handler for
* @handler_fn: Function to call when this event occurs
*
* Associates a specific handler function with an IRC event type.
* The handler will be invoked via O(1) array lookup when the event
* is dispatched. Does nothing if the event type is out of range.
*/
void handler_register(struct handler *handler, enum event_type type,
event_handler_fn handler_fn)
{
if (type < 0 || type >= KIRC_EVENT_TYPE_MAX) {
return;
}
handler->handlers[type] = handler_fn;
}
/**
* handler_init() - Initialize the handler dispatch system
* @handler: Handler structure to initialize
* @ctx: IRC context structure
*
* Initializes the handler system by zeroing the structure and associating
* it with an IRC context. All event handlers are initially NULL.
*
* Return: 0 on success, -1 if handler or ctx is NULL
*/
int handler_init(struct handler *handler, struct kirc_context *ctx)
{
if ((handler == NULL) || (ctx == NULL)) {
return -1;
}
memset(handler, 0, sizeof(*handler));
handler->ctx = ctx;
return 0;
}
/**
* handler_dispatch() - Dispatch event to appropriate handler
* @handler: Handler structure containing registered handlers
* @network: Network connection for the event
* @event: Event to dispatch
* @output: Output buffer for handler responses
*
* Dispatches an IRC event to its registered handler using O(1) array lookup.
* If no specific handler is registered, calls the default handler (typically
* displays raw message). Does nothing if required parameters are NULL.
*/
void handler_dispatch(struct handler *handler, struct network *network,
struct event *event, struct output *output)
{
if (handler == NULL || network == NULL || event == NULL) {
return;
}
/* O(1) direct array lookup instead of O(n) linear search */
if (event->type >= 0 && event->type < KIRC_EVENT_TYPE_MAX) {
event_handler_fn fn = handler->handlers[event->type];
if (fn != NULL) {
fn(network, event, output);
return;
}
}
/* If no handler found, display raw message */
if (handler->default_handler != NULL) {
handler->default_handler(network, event, output);
}
}
================================================
FILE: src/helper.c
================================================
/*
* helper.c
* Helper functions
* Author: Michael Czigler
* License: MIT
*/
#include "helper.h"
/**
* safecpy() - Safe string copy with guaranteed null termination
* @s1: Destination buffer
* @s2: Source string
* @n: Size of destination buffer
*
* Copies a string ensuring the destination is always null-terminated.
* Safer alternative to strncpy. Copies at most n-1 characters and
* always adds a null terminator.
*
* Return: 0 on success, -1 if parameters are invalid
*/
int safecpy(char *s1, const char *s2, size_t n)
{
if ((s1 == NULL) || (s2 == NULL) || (n == 0)) {
return -1;
}
strncpy(s1, s2, n - 1);
s1[n - 1] = '\0';
return 0;
}
/**
* memzero() - Securely zero memory
* @s: Memory buffer to zero
* @n: Number of bytes to zero
*
* Securely overwrites memory with zeros, preventing compiler optimization
* from removing the operation. Used for clearing sensitive data like
* passwords. Uses volatile pointer to ensure the write is not optimized away.
*
* Return: 0 on success, -1 if parameters are invalid
*/
int memzero(void *s, size_t n)
{
if ((s == NULL) || (n == 0)) {
return -1;
}
/* use volatile to prevent compiler optimization */
volatile unsigned char *p = s;
while (n--) {
*p++ = 0;
}
return 0;
}
/**
* find_message_end() - Find IRC message terminator in buffer
* @buffer: Buffer containing IRC protocol data
* @len: Length of the buffer
*
* Searches for the IRC message delimiter (\r\n) while properly handling
* CTCP sequences. CTCP commands are delimited by \001 characters, and
* message terminators within CTCP sequences are ignored. This prevents
* premature message splitting when CTCP data contains \r\n.
*
* Return: Pointer to \r character of the terminator, or NULL if not found
*/
char *find_message_end(const char *buffer, size_t len)
{
int ctcp_active = 0;
for (size_t i = 0; i + 1 < len; ++i) {
if (buffer[i] == '\001') {
/* Toggle CTCP state when marker encountered */
ctcp_active = !ctcp_active;
} else if (buffer[i] == '\r' && buffer[i + 1] == '\n') {
/* Message end found only if not inside CTCP sequence */
if (!ctcp_active) {
return (char *)(buffer + i);
}
}
}
return NULL;
}
================================================
FILE: src/main.c
================================================
/*
* main.c
* Main entry point for the kirc IRC client
* Author: Michael Czigler
* License: MIT
*/
#include "config.h"
#include "ctcp.h"
#include "dcc.h"
#include "editor.h"
#include "event.h"
#include "handler.h"
#include "helper.h"
#include "network.h"
#include "output.h"
#include "protocol.h"
#include "terminal.h"
#include "transport.h"
/**
* kirc_register_handlers() - Register all IRC event handlers
* @handler: Handler structure to configure
*
* Registers handler functions for all supported IRC events and CTCP commands.
* Sets the default handler to protocol_raw for displaying unhandled events.
* Maps event types to their corresponding protocol and CTCP handler functions.
*/
static void kirc_register_handlers(struct handler *handler) {
handler_default(handler, protocol_raw);
handler_register(handler, EVENT_CTCP_CLIENTINFO, ctcp_handle_clientinfo);
handler_register(handler, EVENT_CTCP_PING, ctcp_handle_ping);
handler_register(handler, EVENT_CTCP_TIME, ctcp_handle_time);
handler_register(handler, EVENT_CTCP_VERSION, ctcp_handle_version);
handler_register(handler, EVENT_CTCP_ACTION, protocol_ctcp_action);
handler_register(handler, EVENT_CTCP_DCC, protocol_ctcp_info);
handler_register(handler, EVENT_ERROR, protocol_error);
handler_register(handler, EVENT_EXT_CAP, protocol_info);
handler_register(handler, EVENT_EXT_AUTHENTICATE, protocol_authenticate);
handler_register(handler, EVENT_JOIN, protocol_join);
handler_register(handler, EVENT_KICK, protocol_info);
handler_register(handler, EVENT_MODE, protocol_info);
handler_register(handler, EVENT_NICK, protocol_nick);
handler_register(handler, EVENT_NOTICE, protocol_notice);
handler_register(handler, EVENT_PART, protocol_part);
handler_register(handler, EVENT_PING, protocol_ping);
handler_register(handler, EVENT_PRIVMSG, protocol_privmsg);
handler_register(handler, EVENT_QUIT, protocol_noop);
handler_register(handler, EVENT_TOPIC, protocol_info);
handler_register(handler, EVENT_001_RPL_WELCOME, protocol_welcome);
handler_register(handler, EVENT_002_RPL_YOURHOST, protocol_info);
handler_register(handler, EVENT_003_RPL_CREATED, protocol_info);
handler_register(handler, EVENT_004_RPL_MYINFO, protocol_info);
handler_register(handler, EVENT_005_RPL_BOUNCE, protocol_info);
handler_register(handler, EVENT_042_RPL_YOURID, protocol_info);
handler_register(handler, EVENT_200_RPL_TRACELINK, protocol_info);
handler_register(handler, EVENT_201_RPL_TRACECONNECTING, protocol_info);
handler_register(handler, EVENT_202_RPL_TRACEHANDSHAKE, protocol_info);
handler_register(handler, EVENT_203_RPL_TRACEUNKNOWN, protocol_info);
handler_register(handler, EVENT_204_RPL_TRACEOPERATOR, protocol_info);
handler_register(handler, EVENT_205_RPL_TRACEUSER, protocol_info);
handler_register(handler, EVENT_206_RPL_TRACESERVER, protocol_info);
handler_register(handler, EVENT_207_RPL_TRACESERVICE, protocol_info);
handler_register(handler, EVENT_208_RPL_TRACENEWTYPE, protocol_info);
handler_register(handler, EVENT_209_RPL_TRACECLASS, protocol_info);
handler_register(handler, EVENT_211_RPL_STATSLINKINFO, protocol_info);
handler_register(handler, EVENT_212_RPL_STATSCOMMANDS, protocol_info);
handler_register(handler, EVENT_213_RPL_STATSCLINE, protocol_info);
handler_register(handler, EVENT_215_RPL_STATSILINE, protocol_info);
handler_register(handler, EVENT_216_RPL_STATSKLINE, protocol_info);
handler_register(handler, EVENT_218_RPL_STATSYLINE, protocol_info);
handler_register(handler, EVENT_219_RPL_ENDOFSTATS, protocol_info);
handler_register(handler, EVENT_221_RPL_UMODEIS, protocol_info);
handler_register(handler, EVENT_234_RPL_SERVLIST, protocol_info);
handler_register(handler, EVENT_235_RPL_SERVLISTEND, protocol_info);
handler_register(handler, EVENT_241_RPL_STATSLLINE, protocol_info);
handler_register(handler, EVENT_242_RPL_STATSUPTIME, protocol_info);
handler_register(handler, EVENT_243_RPL_STATSOLINE, protocol_info);
handler_register(handler, EVENT_244_RPL_STATSHLINE, protocol_info);
handler_register(handler, EVENT_245_RPL_STATSSLINE, protocol_info);
handler_register(handler, EVENT_250_RPL_STATSCONN, protocol_info);
handler_register(handler, EVENT_251_RPL_LUSERCLIENT, protocol_info);
handler_register(handler, EVENT_252_RPL_LUSEROP, protocol_info);
handler_register(handler, EVENT_253_RPL_LUSERUNKNOWN, protocol_info);
handler_register(handler, EVENT_254_RPL_LUSERCHANNELS, protocol_info);
handler_register(handler, EVENT_255_RPL_LUSERME, protocol_info);
handler_register(handler, EVENT_256_RPL_ADMINME, protocol_info);
handler_register(handler, EVENT_257_RPL_ADMINLOC1, protocol_info);
handler_register(handler, EVENT_258_RPL_ADMINLOC2, protocol_info);
handler_register(handler, EVENT_259_RPL_ADMINEMAIL, protocol_info);
handler_register(handler, EVENT_261_RPL_TRACELOG, protocol_info);
handler_register(handler, EVENT_263_RPL_TRYAGAIN, protocol_info);
handler_register(handler, EVENT_265_RPL_LOCALUSERS, protocol_info);
handler_register(handler, EVENT_266_RPL_GLOBALUSERS, protocol_info);
handler_register(handler, EVENT_300_RPL_NONE, protocol_info);
handler_register(handler, EVENT_301_RPL_AWAY, protocol_info);
handler_register(handler, EVENT_302_RPL_USERHOST, protocol_info);
handler_register(handler, EVENT_303_RPL_ISON, protocol_info);
handler_register(handler, EVENT_305_RPL_UNAWAY, protocol_info);
handler_register(handler, EVENT_306_RPL_NOWAWAY, protocol_info);
handler_register(handler, EVENT_311_RPL_WHOISUSER, protocol_info);
handler_register(handler, EVENT_312_RPL_WHOISSERVER, protocol_info);
handler_register(handler, EVENT_313_RPL_WHOISOPERATOR, protocol_info);
handler_register(handler, EVENT_314_RPL_WHOWASUSER, protocol_info);
handler_register(handler, EVENT_315_RPL_ENDOFWHO, protocol_info);
handler_register(handler, EVENT_317_RPL_WHOISIDLE, protocol_info);
handler_register(handler, EVENT_318_RPL_ENDOFWHOIS, protocol_info);
handler_register(handler, EVENT_319_RPL_WHOISCHANNELS, protocol_info);
handler_register(handler, EVENT_322_RPL_LIST, protocol_info);
handler_register(handler, EVENT_323_RPL_LISTEND, protocol_info);
handler_register(handler, EVENT_324_RPL_CHANNELMODEIS, protocol_info);
handler_register(handler, EVENT_328_RPL_CHANNEL_URL, protocol_info);
handler_register(handler, EVENT_331_RPL_NOTOPIC, protocol_info);
handler_register(handler, EVENT_332_RPL_TOPIC, protocol_info);
handler_register(handler, EVENT_333_RPL_TOPICWHOTIME, protocol_info);
handler_register(handler, EVENT_341_RPL_INVITING, protocol_info);
handler_register(handler, EVENT_346_RPL_INVITELIST, protocol_info);
handler_register(handler, EVENT_347_RPL_ENDOFINVITELIST, protocol_info);
handler_register(handler, EVENT_348_RPL_EXCEPTLIST, protocol_info);
handler_register(handler, EVENT_349_RPL_ENDOFEXCEPTLIST, protocol_info);
handler_register(handler, EVENT_351_RPL_VERSION, protocol_info);
handler_register(handler, EVENT_352_RPL_WHOREPLY, protocol_info);
handler_register(handler, EVENT_353_RPL_NAMREPLY, protocol_info);
handler_register(handler, EVENT_364_RPL_LINKS, protocol_info);
handler_register(handler, EVENT_365_RPL_ENDOFLINKS, protocol_info);
handler_register(handler, EVENT_366_RPL_ENDOFNAMES, protocol_info);
handler_register(handler, EVENT_367_RPL_BANLIST, protocol_info);
handler_register(handler, EVENT_368_RPL_ENDOFBANLIST, protocol_info);
handler_register(handler, EVENT_369_RPL_ENDOFWHOWAS, protocol_info);
handler_register(handler, EVENT_371_RPL_INFO, protocol_info);
handler_register(handler, EVENT_372_RPL_MOTD, protocol_info);
handler_register(handler, EVENT_374_RPL_ENDOFINFO, protocol_info);
handler_register(handler, EVENT_375_RPL_MOTDSTART, protocol_info);
handler_register(handler, EVENT_376_RPL_ENDOFMOTD, protocol_info);
handler_register(handler, EVENT_381_RPL_YOUREOPER, protocol_info);
handler_register(handler, EVENT_382_RPL_REHASHING, protocol_info);
handler_register(handler, EVENT_383_RPL_YOURESERVICE, protocol_info);
handler_register(handler, EVENT_391_RPL_TIME, protocol_info);
handler_register(handler, EVENT_392_RPL_USERSSTART, protocol_info);
handler_register(handler, EVENT_393_RPL_USERS, protocol_info);
handler_register(handler, EVENT_394_RPL_ENDOFUSERS, protocol_info);
handler_register(handler, EVENT_395_RPL_NOUSERS, protocol_info);
handler_register(handler, EVENT_396_RPL_HOSTHIDDEN, protocol_info);
handler_register(handler, EVENT_400_ERR_UNKNOWNERROR, protocol_error);
handler_register(handler, EVENT_401_ERR_NOSUCHNICK, protocol_error);
handler_register(handler, EVENT_402_ERR_NOSUCHSERVER, protocol_error);
handler_register(handler, EVENT_403_ERR_NOSUCHCHANNEL, protocol_error);
handler_register(handler, EVENT_404_ERR_CANNOTSENDTOCHAN, protocol_error);
handler_register(handler, EVENT_405_ERR_TOOMANYCHANNELS, protocol_error);
handler_register(handler, EVENT_406_ERR_WASNOSUCHNICK, protocol_error);
handler_register(handler, EVENT_407_ERR_TOOMANYTARGETS, protocol_error);
handler_register(handler, EVENT_408_ERR_NOSUCHSERVICE, protocol_error);
handler_register(handler, EVENT_409_ERR_NOORIGIN, protocol_error);
handler_register(handler, EVENT_411_ERR_NORECIPIENT, protocol_error);
handler_register(handler, EVENT_412_ERR_NOTEXTTOSEND, protocol_error);
handler_register(handler, EVENT_413_ERR_NOTOPLEVEL, protocol_error);
handler_register(handler, EVENT_414_ERR_WILDTOPLEVEL, protocol_error);
handler_register(handler, EVENT_415_ERR_BADMASK, protocol_error);
handler_register(handler, EVENT_421_ERR_UNKNOWNCOMMAND, protocol_error);
handler_register(handler, EVENT_422_ERR_NOMOTD, protocol_error);
handler_register(handler, EVENT_423_ERR_NOADMININFO, protocol_error);
handler_register(handler, EVENT_424_ERR_FILEERROR, protocol_error);
handler_register(handler, EVENT_431_ERR_NONICKNAMEGIVEN, protocol_error);
handler_register(handler, EVENT_432_ERR_ERRONEUSNICKNAME, protocol_error);
handler_register(handler, EVENT_433_ERR_NICKNAMEINUSE, protocol_error);
handler_register(handler, EVENT_436_ERR_NICKCOLLISION, protocol_error);
handler_register(handler, EVENT_441_ERR_USERNOTINCHANNEL, protocol_error);
handler_register(handler, EVENT_442_ERR_NOTONCHANNEL, protocol_error);
handler_register(handler, EVENT_443_ERR_USERONCHANNEL, protocol_error);
handler_register(handler, EVENT_444_ERR_NOLOGIN, protocol_error);
handler_register(handler, EVENT_445_ERR_SUMMONDISABLED, protocol_error);
handler_register(handler, EVENT_446_ERR_USERSDISABLED, protocol_error);
handler_register(handler, EVENT_451_ERR_NOTREGISTERED, protocol_error);
handler_register(handler, EVENT_461_ERR_NEEDMOREPARAMS, protocol_error);
handler_register(handler, EVENT_462_ERR_ALREADYREGISTERED, protocol_error);
handler_register(handler, EVENT_463_ERR_NOPERMFORHOST, protocol_error);
handler_register(handler, EVENT_464_ERR_PASSWDMISMATCH, protocol_error);
handler_register(handler, EVENT_465_ERR_YOUREBANNEDCREEP, protocol_error);
handler_register(handler, EVENT_467_ERR_KEYSET, protocol_error);
handler_register(handler, EVENT_470_ERR_LINKCHANNEL, protocol_error);
handler_register(handler, EVENT_471_ERR_CHANNELISFULL, protocol_error);
handler_register(handler, EVENT_472_ERR_UNKNOWNMODE, protocol_error);
handler_register(handler, EVENT_473_ERR_INVITEONLYCHAN, protocol_error);
handler_register(handler, EVENT_474_ERR_BANNEDFROMCHAN, protocol_error);
handler_register(handler, EVENT_475_ERR_BADCHANNELKEY, protocol_error);
handler_register(handler, EVENT_476_ERR_BADCHANMASK, protocol_error);
handler_register(handler, EVENT_477_ERR_NEEDREGGEDNICK, protocol_error);
handler_register(handler, EVENT_478_ERR_BANLISTFULL, protocol_error);
handler_register(handler, EVENT_481_ERR_NOPRIVILEGES, protocol_error);
handler_register(handler, EVENT_482_ERR_CHANOPRIVSNEEDED, protocol_error);
handler_register(handler, EVENT_483_ERR_CANTKILLSERVER, protocol_error);
handler_register(handler, EVENT_485_ERR_UNIQOPRIVSNEEDED, protocol_error);
handler_register(handler, EVENT_491_ERR_NOOPERHOST, protocol_error);
handler_register(handler, EVENT_501_ERR_UMODEUNKNOWNFLAG, protocol_error);
handler_register(handler, EVENT_502_ERR_USERSDONTMATCH, protocol_error);
handler_register(handler, EVENT_524_ERR_HELPNOTFOUND, protocol_error);
handler_register(handler, EVENT_704_RPL_HELPSTART, protocol_info);
handler_register(handler, EVENT_705_RPL_HELPTXT, protocol_info);
handler_register(handler, EVENT_706_RPL_ENDOFHELP, protocol_info);
handler_register(handler, EVENT_900_RPL_LOGGEDIN, protocol_info);
handler_register(handler, EVENT_901_RPL_LOGGEDOUT, protocol_info);
handler_register(handler, EVENT_902_ERR_NICKLOCKED, protocol_error);
handler_register(handler, EVENT_903_RPL_SASLSUCCESS, protocol_info);
handler_register(handler, EVENT_904_ERR_SASLFAIL, protocol_error);
handler_register(handler, EVENT_905_ERR_SASLTOOLONG, protocol_error);
handler_register(handler, EVENT_906_ERR_SASLABORTED, protocol_error);
handler_register(handler, EVENT_907_ERR_SASLALREADY, protocol_error);
handler_register(handler, EVENT_908_RPL_SASLMECHS, protocol_info);
}
/**
* kirc_run() - Main IRC client event loop
* @ctx: IRC context structure with connection settings
*
* Initializes all subsystems (editor, transport, network, DCC, handlers,
* terminal, output), establishes the IRC connection, and runs the main
* event loop. Polls stdin and network socket for events, processing user
* input and IRC messages. Handles terminal raw mode and cleanup on exit.
*
* Return: 0 on clean exit, -1 on initialization or runtime error
*/
static int kirc_run(struct kirc_context *ctx)
{
struct editor editor;
if (editor_init(&editor, ctx) < 0) {
fprintf(stderr, "editor_init failed\n");
return -1;
}
struct transport transport;
if (transport_init(&transport, ctx) < 0) {
fprintf(stderr, "transport_init failed\n");
return -1;
}
struct network network;
if (network_init(&network, &transport, ctx) < 0) {
fprintf(stderr, "network_init failed\n");
return -1;
}
struct dcc dcc;
if (dcc_init(&dcc, ctx) < 0) {
fprintf(stderr, "dcc_init failed\n");
network_free(&network);
return -1;
}
struct handler handler;
if (handler_init(&handler, ctx) < 0) {
fprintf(stderr, "handler_init failed\n");
dcc_free(&dcc);
network_free(&network);
return -1;
}
kirc_register_handlers(&handler);
if (network_connect(&network) < 0) {
fprintf(stderr, "network_connect failed\n");
dcc_free(&dcc);
network_free(&network);
return -1;
}
if (network_send_credentials(&network) < 0) {
fprintf(stderr, "network_send_credentials failed\n");
dcc_free(&dcc);
network_free(&network);
return -1;
}
size_t siz = sizeof(ctx->target);
safecpy(ctx->target, ctx->channels[0], siz);
struct terminal terminal;
if (terminal_init(&terminal, ctx) < 0) {
fprintf(stderr, "terminal_init failed\n");
dcc_free(&dcc);
network_free(&network);
return -1;
}
struct output output;
if (output_init(&output, ctx) < 0) {
fprintf(stderr, "output_init failed\n");
dcc_free(&dcc);
network_free(&network);
return -1;
}
if (terminal_enable_raw(&terminal) < 0) {
fprintf(stderr, "terminal_enable_raw failed\n");
terminal_disable_raw(&terminal);
dcc_free(&dcc);
network_free(&network);
return -1;
}
struct pollfd fds[2] = {
{ .fd = STDIN_FILENO, .events = POLLIN },
{ .fd = network.transport->fd, .events = POLLIN }
};
for (;;) {
int rc = poll(fds, 2, -1);
if (rc == -1) {
if (errno == EINTR) {
continue;
}
terminal_disable_raw(&terminal);
fprintf(stderr, "poll error: %s\n",
strerror(errno));
break;
}
if (rc == 0) {
continue;
}
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
terminal_disable_raw(&terminal);
fprintf(stderr, "stdin error or hangup\n");
break;
}
if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
terminal_disable_raw(&terminal);
fprintf(stderr, "network connection error or closed\n");
break;
}
if (fds[0].revents & POLLIN) {
editor_process_key(&editor);
if (editor.state == EDITOR_STATE_TERMINATE)
break;
if (editor.state == EDITOR_STATE_SEND) {
char *msg = editor_last_entry(&editor);
network_command_handler(&network, msg, &output);
output_flush(&output);
}
editor_handle(&editor);
}
if (fds[1].revents & POLLIN) {
int recv = network_receive(&network);
if (recv < 0) {
terminal_disable_raw(&terminal);
fprintf(stderr, "network_receive error\n");
break;
}
if (recv > 0) {
char *msg = network.buffer;
size_t remaining = network.len;
for (;;) {
char *eol = find_message_end(msg, remaining);
if (!eol) break; /* no complete message found */
*eol = '\0';
struct event event;
event_init(&event, ctx);
event_parse(&event, msg);
handler_dispatch(&handler, &network, &event, &output);
dcc_handle(&dcc, &network, &event);
msg = eol + 2;
remaining = network.buffer + network.len - msg;
}
if (remaining > 0) {
if ((remaining < sizeof(network.buffer)) &&
(msg > network.buffer)) {
memmove(network.buffer, msg, remaining);
}
network.len = remaining;
} else {
network.len = 0;
}
output_flush(&output);
editor_handle(&editor);
}
}
dcc_process(&dcc);
}
terminal_disable_raw(&terminal);
dcc_free(&dcc);
network_free(&network);
return 0;
}
/**
* main() - Program entry point
* @argc: Argument count
* @argv: Argument vector
*
* Initializes configuration from environment and command-line arguments,
* runs the IRC client, and cleans up on exit. Ensures secure cleanup of
* sensitive data.
*
* Return: EXIT_SUCCESS on normal termination, EXIT_FAILURE on error
*/
int main(int argc, char *argv[])
{
struct kirc_context ctx;
if (config_init(&ctx) < 0) {
return EXIT_FAILURE;
}
if (config_parse_args(&ctx, argc, argv) < 0) {
config_free(&ctx);
return EXIT_FAILURE;
}
if (kirc_run(&ctx) < 0) {
config_free(&ctx);
return EXIT_FAILURE;
}
config_free(&ctx);
return EXIT_SUCCESS;
}
================================================
FILE: src/network.c
================================================
/*
* network.c
* Network connection management
* Author: Michael Czigler
* License: MIT
*/
#include "network.h"
/**
* network_send() - Send formatted message to IRC server
* @network: Network connection structure
* @fmt: printf-style format string
* @...: Variable arguments for format string
*
* Constructs and sends a formatted message to the IRC server through the
* transport layer. Messages are limited to MESSAGE_MAX_LEN bytes.
*
* Return: 0 on success, -1 if network is NULL or send fails
*/
int network_send(struct network *network, const char *fmt, ...)
{
if (network == NULL) {
return -1;
}
if (network->transport == NULL) {
return -1;
}
char buf[MESSAGE_MAX_LEN];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
size_t len = strnlen(buf, sizeof(buf));
ssize_t nsent = transport_send(
network->transport, buf, len);
if (nsent < 0) {
return -1;
}
return 0;
}
/**
* network_receive() - Receive data from IRC server
* @network: Network connection structure
*
* Reads available data from the server into the network buffer. Handles
* non-blocking I/O and partial reads. Updates the buffer length with
* newly received data. Returns immediately if no data is available.
*
* Return: Number of bytes received, 0 if would block, -1 on error or disconnect
*/
int network_receive(struct network *network)
{
size_t buffer_n = sizeof(network->buffer) - 1;
ssize_t nread = transport_receive(
network->transport,
network->buffer + network->len,
buffer_n - network->len);
if (nread < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
} else {
return -1;
}
}
if (nread == 0) {
return -1;
}
network->len += (int)nread;
network->buffer[network->len] = '\0';
return nread;
}
/**
* network_connect() - Establish connection to IRC server
* @network: Network connection structure
*
* Initiates connection to the IRC server using the transport layer.
* Connection details (server, port) are taken from the context.
*
* Return: 0 on success, -1 on connection failure
*/
int network_connect(struct network *network)
{
return transport_connect(network->transport);
}
/**
* network_send_private_msg() - Send private message to user
* @network: Network connection structure
* @msg: Message string in format "username message text"
* @output: Output buffer for display feedback
*
* Parses and sends a private message (PRIVMSG) to a specific user.
* Expected format: "username message text". Validates message length
* and displays sent message to user.
*/
static void network_send_private_msg(struct network *network,
char *msg, struct output *output)
{
char msg_copy[MESSAGE_MAX_LEN];
safecpy(msg_copy, msg, sizeof(msg_copy));
char *username = strtok(msg_copy, " ");
if (username == NULL) {
const char *err = "error: message malformed";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
char *message = strtok(NULL, "");
if (message == NULL) {
const char *err = "error: message malformed";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
size_t overhead = strlen(username) + 13;
size_t msg_len = strlen(message);
if (overhead + msg_len >= MESSAGE_MAX_LEN) {
const char *err = "error: message too long";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
network_send(network, "PRIVMSG %s :%s\r\n",
username, message);
output_append(output, "\rto " BOLD_RED "%s" RESET ": %s" CLEAR_LINE "\r\n",
username, message);
}
/**
* network_send_topic() - Send TOPIC command for channel
* @network: Network connection structure
* @msg: Topic command arguments (channel and optional new topic)
* @output: Output buffer for error messages
*
* Parses and sends IRC TOPIC command. If only channel is provided,
* requests the current topic. If topic text is included, sets a new topic.
* Displays usage error if channel is missing.
*/
static void network_send_topic(struct network *network,
char *msg, struct output *output)
{
char msg_copy[MESSAGE_MAX_LEN];
safecpy(msg_copy, msg, sizeof(msg_copy));
char *channel = strtok(msg_copy, " ");
if (channel == NULL) {
const char *err = "usage: /topic <channel> [<topic>]";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
char *topic = strtok(NULL, "");
if (topic == NULL) {
network_send(network, "TOPIC %s\r\n", channel);
return;
}
network_send(network, "TOPIC %s :%s\r\n", channel, topic);
}
/**
* network_send_ctcp_action() - Send CTCP ACTION to current target
* @network: Network connection structure
* @msg: Action text to send
* @output: Output buffer for display feedback
*
* Sends a CTCP ACTION message (/me command) to the current target channel
* or user. Validates message length and requires a target to be set.
*/
static void network_send_ctcp_action(struct network *network,
char *msg, struct output *output)
{
if (network->ctx->target[0] != '\0') {
/* Validate total message length: "PRIVMSG " + target + " :\001ACTION " + msg + "\001\r\n" */
size_t overhead = 10 + strlen(network->ctx->target) + 10 + 3; /* "PRIVMSG " + target + " :\001ACTION " + "\001\r\n" */
size_t msg_len = strlen(msg);
if (overhead + msg_len >= MESSAGE_MAX_LEN) {
const char *err = "error: message too long";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
network_send(network,
"PRIVMSG %s :\001ACTION %s\001\r\n",
network->ctx->target, msg);
output_append(output, "\rto \u2022 " BOLD "%s" RESET ": %s" CLEAR_LINE "\r\n",
network->ctx->target, msg);
} else {
const char *err = "error: no channel set";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
}
}
/**
* network_send_ctcp_command() - Send CTCP command to target
* @network: Network connection structure
* @msg: Message in format "target command [args]"
* @output: Output buffer for display feedback
*
* Parses and sends a CTCP command to a specified target. Expected format:
* "target command [args]". Validates input and provides usage feedback.
*/
static void network_send_ctcp_command(struct network *network,
char *msg, struct output *output)
{
char msg_copy[MESSAGE_MAX_LEN];
safecpy(msg_copy, msg, sizeof(msg_copy));
char *target = strtok(msg_copy, " ");
if (target == NULL) {
const char *err = "usage: /ctcp <nick> <command>";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
char *command = strtok(NULL, "");
if (command == NULL) {
const char *err = "usage: /ctcp <nick> <command>";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
network_send(network, "PRIVMSG %s :\001%s\001\r\n",
target, command);
output_append(output, "\rctcp: " BOLD_RED "%s" RESET ": %s" CLEAR_LINE "\r\n",
target, command);
}
/**
* network_send_channel_msg() - Send message to current target channel
* @network: Network connection structure
* @msg: Message text to send
* @output: Output buffer for display feedback
*
* Sends a channel message to the currently set target. Validates message
* length and requires a target to be set. Displays sent message locally.
*/
static void network_send_channel_msg(
struct network *network, char *msg, struct output *output)
{
if (network->ctx->target[0] != '\0') {
/* Validate total message length: "PRIVMSG " + target + " :" + msg + "\r\n" */
size_t overhead = 10 + strlen(network->ctx->target) + 3 + 2; /* "PRIVMSG " + target + " :" + "\r\n" */
size_t msg_len = strlen(msg);
if (overhead + msg_len >= MESSAGE_MAX_LEN) {
const char *err = "error: message too long";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
return;
}
network_send(network, "PRIVMSG %s :%s\r\n",
network->ctx->target, msg);
output_append(output, "\rto " BOLD "%s" RESET ": %s" CLEAR_LINE "\r\n",
network->ctx->target, msg);
} else {
const char *err = "error: no channel set";
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET "\r\n", err);
}
}
/**
* network_command_handler() - Process user input commands
* @network: Network connection structure
* @msg: User input message to process
* @output: Output buffer for display feedback
*
* Routes user input to appropriate handlers based on prefix:
* / - IRC commands (/set, /me, /ctcp, or raw IRC commands)
* @ - Private messages to users
* (default) - Channel messages to current target
* Parses commands and delegates to specialized send functions.
*
* Return: 0 on success
*/
int network_command_handler(struct network *network, char *msg, struct output *output)
{
switch (msg[0]) {
case '/': /* system command message */
switch (msg[1]) {
case 's': /* set target (channel or nickname) */
if (strncmp(msg + 1, "set ", 4) == 0) {
size_t siz = sizeof(network->ctx->target);
safecpy(network->ctx->target, msg + 5, siz);
} else {
network_send(network, "%s\r\n", msg + 1);
}
break;
case 't': /* send TOPIC command */
if (strncmp(msg + 1, "topic ", 6) == 0) {
network_send_topic(network, msg + 7, output);
} else {
network_send(network, "%s\r\n", msg + 1);
}
break;
case 'm': /* send CTCP ACTION to target */
if (strncmp(msg + 1, "me ", 3) == 0) {
network_send_ctcp_action(network, msg + 4, output);
} else {
network_send(network, "%s\r\n", msg + 1);
}
break;
case 'c': /* send CTCP command */
if (strncmp(msg + 1, "ctcp ", 5) == 0) {
network_send_ctcp_command(network, msg + 6, output);
} else {
network_send(network, "%s\r\n", msg + 1);
}
break;
default: /* send raw server command */
network_send(network, "%s\r\n", msg + 1);
break;
}
break;
case '@': /* private message */
network_send_private_msg(network, msg + 1, output);
break;
default: /* channel message */
network_send_channel_msg(network, msg, output);
break;
}
return 0;
}
/**
* network_send_credentials() - Send authentication credentials to server
* @network: Network connection structure
*
* Sends initial authentication sequence to IRC server including SASL
* capability negotiation (if configured), NICK, USER commands, and
* optionally PASS (server password). Initiates SASL authentication
* for EXTERNAL or PLAIN mechanisms.
*
* Return: 0 on success, -1 if SASL mechanism is unrecognized
*/
int network_send_credentials(struct network *network)
{
if (network->ctx->mechanism != SASL_NONE) {
network_send(network, "CAP REQ :sasl\r\n");
}
network_send(network, "NICK %s\r\n",
network->ctx->nickname);
char *username, *realname;
if (network->ctx->username[0] != '\0') {
username = network->ctx->username;
} else {
username = network->ctx->nickname;
}
if (network->ctx->realname[0] != '\0') {
realname = network->ctx->realname;
} else {
realname = network->ctx->nickname;
}
network_send(network, "USER %s - - :%s\r\n",
username, realname);
if (network->ctx->mechanism != SASL_NONE) {
if (network->ctx->mechanism == SASL_EXTERNAL) {
network_send(network, "AUTHENTICATE EXTERNAL\r\n");
} else if (network->ctx->mechanism == SASL_PLAIN) {
network_send(network, "AUTHENTICATE PLAIN\r\n");
} else {
fprintf(stderr, "unrecognized SASL mechanism\n");
return -1;
}
}
if (network->ctx->password[0] != '\0') {
network_send(network, "PASS %s\r\n",
network->ctx->password);
}
return 0;
}
/**
* network_init() - Initialize network connection structure
* @network: Network structure to initialize
* @transport: Transport layer instance
* @ctx: IRC context structure
*
* Initializes the network management structure, associating it with a
* transport layer and IRC context. Zeros the buffer and state.
*
* Return: 0 on success, -1 if any parameter is NULL
*/
int network_init(struct network *network,
struct transport *transport, struct kirc_context *ctx)
{
if ((network == NULL) || (transport == NULL) || (ctx == NULL)) {
return -1;
}
memset(network, 0, sizeof(*network));
network->ctx = ctx;
network->transport = transport;
return 0;
}
/**
* network_free() - Free network resources
* @network: Network structure to clean up
*
* Releases resources associated with the network connection by freeing
* the transport layer.
*
* Return: 0 on success, -1 if transport cleanup fails
*/
int network_free(struct network *network)
{
if (transport_free(network->transport) < 0) {
return -1;
}
return 0;
}
================================================
FILE: src/output.c
================================================
/*
* output.c
* Buffered output for improved terminal responsiveness
* Author: Michael Czigler
* License: MIT
*/
#include "output.h"
/**
* output_init() - Initialize output buffer
* @output: Output structure to initialize
* @ctx: IRC context structure
*
* Initializes the output buffer system, preparing it for buffered writes
* to stdout. Associates the output with an IRC context.
*
* Return: 0 on success, -1 if output or ctx is NULL
*/
int output_init(struct output *output,
struct kirc_context *ctx)
{
if ((output == NULL) || (ctx == NULL)) {
return -1;
}
memset(output, 0, sizeof(*output));
output->ctx = ctx;
output->len = 0;
output->buffer[0] = '\0';
return 0;
}
/**
* output_append() - Append formatted text to output buffer
* @output: Output buffer structure
* @fmt: printf-style format string
* @...: Variable arguments for format string
*
* Appends formatted text to the output buffer. Automatically flushes if
* buffer becomes nearly full (within 256 bytes of capacity). If text
* doesn't fit, flushes buffer and retries. Handles truncation for
* extremely long messages.
*
* Return: 0 on success, -1 on error
*/
int output_append(struct output *output,
const char *fmt, ...)
{
if (output == NULL || fmt == NULL) {
return -1;
}
/* Check if buffer has space for at least a reasonable message */
if (output->len >= KIRC_OUTPUT_BUFFER_SIZE - 256) {
/* Auto-flush if buffer is nearly full */
output_flush(output);
}
va_list ap;
va_start(ap, fmt);
int remaining = KIRC_OUTPUT_BUFFER_SIZE - output->len;
int written = vsnprintf(output->buffer + output->len,
remaining, fmt, ap);
va_end(ap);
if (written < 0) {
return -1;
}
if (written >= remaining) {
/* Message was truncated, flush and try again */
output_flush(output);
va_start(ap, fmt);
written = vsnprintf(output->buffer,
KIRC_OUTPUT_BUFFER_SIZE, fmt, ap);
va_end(ap);
if (written < 0 || written >= KIRC_OUTPUT_BUFFER_SIZE) {
return -1;
}
output->len = written;
} else {
output->len += written;
}
return 0;
}
/**
* output_flush() - Write buffered output to stdout
* @output: Output buffer structure
*
* Writes the entire buffered content to stdout in a single system call
* and resets the buffer. Improves performance by reducing write() calls.
* Ignores write errors.
*/
void output_flush(struct output *output)
{
if (output == NULL || output->len == 0) {
return;
}
/* Write entire buffer in one system call */
ssize_t written = write(STDOUT_FILENO,
output->buffer, output->len);
(void)written; /* Ignore errors */
output->len = 0;
output->buffer[0] = '\0';
}
/**
* output_clear() - Clear output buffer without writing
* @output: Output buffer structure
*
* Discards all buffered content without writing it to stdout. Resets
* the buffer to empty state.
*/
void output_clear(struct output *output)
{
if (output == NULL) {
return;
}
output->len = 0;
output->buffer[0] = '\0';
}
/**
* output_pending() - Check if output buffer has data
* @output: Output buffer structure
*
* Returns the number of bytes currently buffered but not yet written
* to stdout.
*
* Return: Number of buffered bytes, or 0 if output is NULL
*/
int output_pending(struct output *output)
{
if (output == NULL) {
return 0;
}
return output->len;
}
================================================
FILE: src/protocol.c
================================================
/*
* protocol.c
* IRC protocol handling
* Author: Michael Czigler
* License: MIT
*/
#include "protocol.h"
/**
* protocol_get_time() - Get formatted timestamp string
*
* Returns the current local time as a formatted string using the format
* defined by KIRC_TIMESTAMP_FORMAT. Uses a static buffer, so the returned
* pointer is valid until the next call.
*
* Return: Pointer to static timestamp string
*/
static const char *protocol_get_time(void)
{
static char timestamp[KIRC_TIMESTAMP_SIZE];
time_t current;
time(¤t);
struct tm *info = localtime(¤t);
strftime(timestamp, KIRC_TIMESTAMP_SIZE,
KIRC_TIMESTAMP_FORMAT, info);
return timestamp;
}
/**
* protocol_noop() - No-operation event handler
* @network: Network connection (unused)
* @event: Event structure (unused)
* @output: Output buffer (unused)
*
* Handler that does nothing. Used for events that should be silently
* ignored (e.g., JOIN, PART, QUIT).
*/
void protocol_noop(struct network *network, struct event *event, struct output *output)
{
(void)network;
(void)event;
(void)output;
}
/**
* protocol_ping() - Handle PING requests from server
* @network: Network connection structure
* @event: Event containing ping data
* @output: Output buffer (unused)
*
* Responds to server PING messages with appropriate PONG reply to
* maintain connection keepalive.
*/
void protocol_ping(struct network *network, struct event *event, struct output *output)
{
(void)output;
network_send(network, "PONG :%s\r\n", event->message);
}
/**
* network_authenticate_plain() - Send SASL PLAIN authentication
* @network: Network connection structure
*
* Sends base64-encoded SASL PLAIN authentication data in chunks of
* AUTH_CHUNK_SIZE bytes. If the total length is an exact multiple of
* the chunk size, sends a final "+" to indicate completion.
*/
static void network_authenticate_plain(struct network *network)
{
int len = strlen(network->ctx->auth);
for (int offset = 0; offset < len; offset += AUTH_CHUNK_SIZE) {
char chunk[AUTH_CHUNK_SIZE + 1];
safecpy(chunk, network->ctx->auth + offset, sizeof(chunk));
network_send(network, "AUTHENTICATE %s\r\n", chunk);
}
if ((len > 0) && (len % AUTH_CHUNK_SIZE == 0)) {
network_send(network, "AUTHENTICATE +\r\n");
}
}
/**
* protocol_authenticate() - Handle SASL authentication challenge
* @network: Network connection structure
* @event: Authentication event (unused)
* @output: Output buffer (unused)
*
* Responds to server's AUTHENTICATE challenge based on configured SASL
* mechanism (PLAIN or EXTERNAL). Sends authentication data and finalizes
* capability negotiation with CAP END.
*/
void protocol_authenticate(struct network *network, struct event *event, struct output *output)
{
(void)event;
(void)output;
if (network->ctx->auth[0] == '\0') {
network_send(network, "AUTHENTICATE '*'\r\n");
return;
}
switch (network->ctx->mechanism) {
case SASL_PLAIN:
network_authenticate_plain(network);
break;
case SASL_EXTERNAL:
network_send(network, "AUTHENTICATE +\r\n");
break;
default:
network_send(network, "AUTHENTICATE '*'\r\n");
break;
}
if (network->ctx->mechanism != SASL_NONE) {
network_send(network, "CAP END\r\n");
}
}
/**
* protocol_welcome() - Handle RPL_WELCOME (001) server message
* @network: Network connection structure
* @event: Welcome event (unused)
* @output: Output buffer (unused)
*
* Processes the server welcome message by automatically joining all
* configured channels from the context.
*/
void protocol_welcome(struct network *network, struct event *event, struct output *output)
{
(void)event;
(void)output;
for (int i = 0; network->ctx->channels[i][0] != '\0'; ++i) {
network_send(network, "JOIN %s\r\n",
network->ctx->channels[i]);
}
}
/**
* protocol_raw() - Display raw IRC message
* @network: Network connection (unused)
* @event: Event containing raw message
* @output: Output buffer for display
*
* Displays the complete raw IRC message with timestamp. Used as the
* default handler for events without specific handlers.
*/
void protocol_raw(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET
" " REVERSE "%s" RESET "\r\n",
protocol_get_time(), event->raw);
}
/**
* protocol_info() - Display informational message
* @network: Network connection (unused)
* @event: Event containing message
* @output: Output buffer for display
*
* Displays an informational IRC message with timestamp in dimmed text.
* Used for most server replies and status messages.
*/
void protocol_info(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s %s" RESET "\r\n",
protocol_get_time(), event->message);
}
/**
* protocol_error() - Display error message
* @network: Network connection (unused)
* @event: Event containing error message
* @output: Output buffer for display
*
* Displays an IRC error message with timestamp. Error text is shown
* in bold red to distinguish from normal messages.
*/
void protocol_error(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET
" " BOLD_RED "%s" RESET "\r\n",
protocol_get_time(), event->message);
}
/**
* protocol_notice() - Display NOTICE message
* @network: Network connection (unused)
* @event: Event containing notice details
* @output: Output buffer for display
*
* Displays a NOTICE message with the sender's nickname in bold blue
* and timestamp.
*/
void protocol_notice(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET
" " BOLD_BLUE "%s" RESET " %s\r\n",
protocol_get_time(), event->nickname, event->message);
}
/**
* protocol_privmsg_direct() - Display direct private message
* @network: Network connection (unused)
* @event: Event containing private message
* @output: Output buffer for display
*
* Displays a private message sent directly to the user (not in a channel).
* Shows sender in bold blue with message text in blue.
*/
static void protocol_privmsg_direct(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET
" " BOLD_BLUE "%s" RESET " " BLUE "%s" RESET "\r\n",
protocol_get_time(), event->nickname, event->message);
}
/**
* protocol_privmsg_indirect() - Display channel message
* @network: Network connection (unused)
* @event: Event containing channel message
* @output: Output buffer for display
*
* Displays a message sent to a channel. Shows nickname in bold,
* channel name in brackets, and message text.
*/
static void protocol_privmsg_indirect(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s" RESET
" " BOLD "%s" RESET " [%s]: %s\r\n",
protocol_get_time(), event->nickname, event->channel, event->message);
}
/**
* protocol_privmsg() - Route PRIVMSG to appropriate handler
* @network: Network connection structure
* @event: Event containing PRIVMSG details
* @output: Output buffer for display
*
* Determines whether a PRIVMSG is a direct message or channel message
* by comparing the target with the user's nickname, then routes to the
* appropriate display function.
*/
void protocol_privmsg(struct network *network, struct event *event, struct output *output)
{
char *channel = event->channel;
char *nickname = event->ctx->nickname;
if (strcmp(channel, nickname) == 0) {
protocol_privmsg_direct(network, event, output);
} else {
protocol_privmsg_indirect(network, event, output);
}
}
/**
* protocol_nick() - Handle nickname change events
* @network: Network connection (unused)
* @event: Event containing nickname change information
* @output: Output buffer for display
*
* Handles NICK events by displaying the nickname change. If the change
* is for the current user, updates the context with the new nickname.
* Shows appropriate messages for self and other users.
*/
void protocol_nick(struct network *network, struct event *event, struct output *output)
{
(void)network;
struct kirc_context *ctx = event->ctx;
const char *timestamp = protocol_get_time();
if (strcmp(event->nickname, ctx->nickname) == 0) {
size_t siz = sizeof(ctx->nickname) - 1;
strncpy(ctx->nickname, event->message, siz);
ctx->nickname[siz] = '\0';
output_append(output, "\r" CLEAR_LINE
DIM "%s you are now known as %s" RESET "\r\n",
timestamp, event->message);
} else {
output_append(output, "\r" CLEAR_LINE
DIM "%s %s is now known as %s" RESET "\r\n",
timestamp, event->nickname, event->message);
}
}
/**
* protocol_join() - Handle JOIN channel event
* @network: Network connection (unused)
* @event: Event containing JOIN details
* @output: Output buffer for display
*
* Processes IRC JOIN events. If the joining user is the client itself,
* displays a confirmation message. Otherwise, delegates to protocol_noop().
*/
void protocol_join(struct network *network, struct event *event, struct output *output)
{
(void)network;
if (strcmp(event->nickname, event->ctx->nickname) == 0) {
output_append(output, "\r" CLEAR_LINE
DIM "kirc: you've joined %s" RESET "\r\n",
event->channel);
} else {
protocol_noop(network, event, output);
}
}
/**
* protocol_part() - Handle PART channel event
* @network: Network connection (unused)
* @event: Event containing PART details
* @output: Output buffer for display
*
* Processes IRC PART events. If the leaving user is the client itself,
* displays a confirmation message. Otherwise, delegates to protocol_noop().
*/
void protocol_part(struct network *network, struct event *event, struct output *output)
{
(void)network;
if (strcmp(event->nickname, event->ctx->nickname) == 0) {
output_append(output, "\r" CLEAR_LINE
DIM "kirc: you left %s" RESET "\r\n",
event->channel);
} else {
protocol_noop(network, event, output);
}
}
/**
* protocol_ctcp_action() - Display CTCP ACTION message
* @network: Network connection (unused)
* @event: Event containing ACTION details
* @output: Output buffer for display
*
* Displays a CTCP ACTION message (/me command) with a bullet point
* prefix, timestamp, nickname, and action text in dimmed format.
*/
void protocol_ctcp_action(struct network *network, struct event *event, struct output *output)
{
(void)network;
output_append(output, "\r" CLEAR_LINE DIM "%s \u2022 %s %s" RESET "\r\n",
protocol_get_time(), event->nickname, event->message);
}
/**
* protocol_ctcp_info() - Display CTCP informational message
* @network: Network connection (unused)
* @event: Event containing CTCP details
* @output: Output buffer for display
*
* Displays CTCP protocol messages (CLIENTINFO, DCC, PING, TIME, VERSION)
* with sender's nickname in bold blue and appropriate label. Shows
* parameters or message content if present.
*/
void protocol_ctcp_info(struct network *network, struct event *event, struct output *output)
{
(void)network;
const char *label = "";
const char *timestamp = protocol_get_time();
switch(event->type) {
case EVENT_CTCP_CLIENTINFO:
label = "CLIENTINFO";
break;
case EVENT_CTCP_DCC:
label = "DCC";
break;
case EVENT_CTCP_PING:
label = "PING";
break;
case EVENT_CTCP_TIME:
label = "TIME";
break;
case EVENT_CTCP_VERSION:
label = "VERSION";
break;
default:
label = "CTCP";
break;
}
if (event->params[0] != '\0') {
output_append(output, "\r" CLEAR_LINE DIM "%s " RESET
BOLD_BLUE "%s" RESET " %s: %s\r\n",
timestamp, event->nickname, label, event->params);
} else if (event->message[0] != '\0') {
output_append(output, "\r" CLEAR_LINE DIM "%s " RESET
BOLD_BLUE "%s" RESET " %s: %s\r\n",
timestamp, event->nickname, label, event->message);
} else {
output_append(output, "\r" CLEAR_LINE DIM "%s " RESET
BOLD_BLUE "%s" RESET " %s\r\n",
timestamp, event->nickname, label);
}
}
================================================
FILE: src/terminal.c
================================================
/*
* terminal.c
* Terminal interface and display management
* Author: Michael Czigler
* License: MIT
*/
#include "terminal.h"
/**
* terminal_get_cursor_column() - Query terminal cursor column position
* @in_fd: File descriptor to read response from
* @out_fd: File descriptor to send query to
*
* Queries the terminal for the current cursor position using ANSI escape
* sequences and parses the response to extract the column number.
*
* Return: Current column position (1-based), or -1 on error
*/
static int terminal_get_cursor_column(int in_fd, int out_fd)
{
char buf[32];
unsigned int i = 0;
int row = 0, col = 0;
/* Request cursor position */
if (write(out_fd, CURSOR_POS, 4) != 4) {
return -1;
}
while (i < sizeof(buf) - 1) {
if (read(in_fd, buf + i, 1) != 1) {
break;
}
if (buf[i] == 'R') {
break;
}
i++;
}
buf[i] = '\0';
if (buf[0] != 27 || buf[1] != '[') {
return -1;
}
if (sscanf(buf + 2, "%d;%d", &row, &col) != 2) {
return -1;
}
return col;
}
/**
* terminal_columns() - Determine terminal width in columns
* @tty_fd: TTY file descriptor
*
* Determines the terminal width using ioctl(TIOCGWINSZ) if available,
* or falls back to manual probing by moving cursor and querying position.
* Returns KIRC_DEFAULT_COLUMNS if detection fails.
*
* Return: Terminal width in columns
*/
int terminal_columns(int tty_fd)
{
struct winsize ws;
if (ioctl(tty_fd, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 0) {
return ws.ws_col;
}
/* Fallback: manual probing */
int start = terminal_get_cursor_column(tty_fd,
STDOUT_FILENO);
if (start == -1) {
return KIRC_DEFAULT_COLUMNS;
}
/* Move far right */
if (write(STDOUT_FILENO, "\x1b[999C", 6) != 6) {
return KIRC_DEFAULT_COLUMNS;
}
int end = terminal_get_cursor_column(tty_fd,
STDOUT_FILENO);
if (end == -1) {
return KIRC_DEFAULT_COLUMNS;
}
/* Move cursor back */
if (end > start) {
char seq[32];
int diff = end - start;
snprintf(seq, sizeof(seq), "\x1b[%dD", diff);
if (write(STDOUT_FILENO, seq,
strnlen(seq, sizeof(seq))) < 32) {
return KIRC_DEFAULT_COLUMNS;
}
}
return (end > 0) ? end : KIRC_DEFAULT_COLUMNS;
}
/**
* terminal_enable_raw() - Enable raw terminal mode
* @terminal: Terminal state structure
*
* Configures the terminal for raw (non-canonical) input mode, disabling
* line buffering, echo, and signal generation. Saves the original terminal
* settings for later restoration. Required for character-by-character input
* processing.
*
* Return: 0 on success, -1 if stdin is not a TTY or configuration fails
*/
int terminal_enable_raw(struct terminal *terminal)
{
if (!isatty(STDIN_FILENO)) {
return -1;
}
if (terminal->raw_mode_enabled) {
return 0;
}
if (tcgetattr(STDIN_FILENO,
&terminal->original) == -1) {
return -1;
}
struct termios raw = terminal->original;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
return -1;
}
terminal->raw_mode_enabled = 1;
return 0;
}
/**
* terminal_disable_raw() - Restore normal terminal mode
* @terminal: Terminal state structure
*
* Restores the terminal to its original configuration, re-enabling
* line buffering, echo, and signal generation. Should be called before
* program exit or when switching away from raw mode.
*/
void terminal_disable_raw(struct terminal *terminal)
{
if (!terminal->raw_mode_enabled) {
return;
}
tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal->original);
terminal->raw_mode_enabled = 0;
}
/**
* terminal_init() - Initialize terminal management structure
* @terminal: Terminal structure to initialize
* @ctx: IRC context structure
*
* Initializes the terminal state structure, zeroing all fields and
* associating it with an IRC context.
*
* Return: 0 on success, -1 if terminal or ctx is NULL
*/
int terminal_init(struct terminal *terminal,
struct kirc_context *ctx)
{
if ((terminal == NULL) || (ctx == NULL)) {
return -1;
}
memset(terminal, 0, sizeof(*terminal));
terminal->ctx = ctx;
return 0;
}
================================================
FILE: src/transport.c
================================================
/*
* transport.c
* Data transport layer
* Author: Michael Czigler
* License: MIT
*/
#include "transport.h"
/**
* poll_wait_write() - Wait for socket to be writable
* @fd: File descriptor to poll
* @timeout_ms: Timeout in milliseconds
*
* Polls a file descriptor for write readiness, handling EINTR by retrying.
* Used during non-blocking connect() to wait for connection completion.
*
* Return: 0 if ready for write, -1 on timeout or error
*/
static int poll_wait_write(int fd, int timeout_ms)
{
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLOUT;
for (;;) {
int rc = poll(&pfd, 1, timeout_ms);
if (rc > 0)
return 0; /* ready */
if (rc == 0)
return -1; /* timeout */
if (errno == EINTR)
continue;
return -1; /* error */
}
}
/**
* transport_send() - Send data over transport connection
* @transport: Transport structure
* @buffer: Data buffer to send
* @len: Number of bytes to send
*
* Writes data to the transport file descriptor. Does not guarantee
* that all data is sent in one call (partial writes possible).
*
* Return: Number of bytes written, or -1 on error
*/
ssize_t transport_send(struct transport *transport,
const char *buffer, size_t len)
{
if (transport == NULL)
return -1;
if (transport->fd < 0)
return -1;
ssize_t rc = write(transport->fd,
buffer, len);
if (rc < 0)
return -1;
return rc;
}
/**
* transport_receive() - Receive data from transport connection
* @transport: Transport structure
* @buffer: Buffer to store received data
* @len: Maximum number of bytes to receive
*
* Reads data from the transport file descriptor. Handles non-blocking
* I/O by distinguishing between EAGAIN/EWOULDBLOCK (no data available)
* and other errors. Returns -1 on connection close (nread == 0).
*
* Return: Number of bytes read, 0 if would block, -1 on error or disconnect
*/
ssize_t transport_receive(struct transport *transport,
char *buffer, size_t len)
{
if (transport == NULL)
return -1;
if (transport->fd < 0)
return -1;
ssize_t nread = read(transport->fd,
buffer, len);
if (nread < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
} else {
return -1;
}
}
if (nread == 0) {
return -1;
}
return nread;
}
/**
* transport_connect() - Establish transport connection to server
* @transport: Transport structure with server details
*
* Creates a socket and connects to the server specified in the transport
* context (server name and port). Uses getaddrinfo() for address resolution,
* supporting both IPv4 and IPv6. Configures socket for non-blocking I/O
* and handles EINPROGRESS for asynchronous connection with timeout.
*
* Return: 0 on successful connection, -1 on failure
*/
int transport_connect(struct transport *transport)
{
struct addrinfo hints, *res = NULL, *p = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
int status = getaddrinfo(transport->ctx->server,
transport->ctx->port, &hints, &res);
if (status != 0) {
fprintf(stderr, "getaddrinfo: %s\n",
gai_strerror(status));
return -1;
}
transport->fd = -1;
for (p = res; p; p = p->ai_next) {
int fd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol);
if (fd < 0) {
continue;
}
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
close(fd);
continue;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
close(fd);
continue;
}
int rc = connect(fd, p->ai_addr, p->ai_addrlen);
if (rc == 0) {
transport->fd = fd;
break;
}
if (errno == EINPROGRESS) {
int timeout_ms = KIRC_TIMEOUT_MS;
int rc = poll_wait_write(fd, timeout_ms);
if (rc == 0) {
int soerr = 0;
socklen_t slen = sizeof(soerr);
getsockopt(fd, SOL_SOCKET, SO_ERROR,
&soerr, &slen);
if (soerr == 0) {
transport->fd = fd;
break;
}
}
}
close(fd);
}
freeaddrinfo(res);
if (transport->fd < 0) {
return -1;
}
return 0;
}
/**
* transport_init() - Initialize transport structure
* @transport: Transport structure to initialize
* @ctx: IRC context containing connection details
*
* Initializes the transport layer, zeroing the structure and setting
* the file descriptor to -1 (not connected). Associates transport with
* IRC context for server and port information.
*
* Return: 0 on success, -1 if transport or ctx is NULL
*/
int transport_init(struct transport *transport,
struct kirc_context *ctx)
{
if ((transport == NULL) || (ctx == NULL)) {
return -1;
}
memset(transport, 0, sizeof(*transport));
transport->ctx = ctx;
transport->fd = -1;
return 0;
}
/**
* transport_free() - Close and cleanup transport connection
* @transport: Transport structure to free
*
* Closes the transport socket if open and resets the file descriptor
* to -1. Should be called during cleanup to release resources.
*
* Return: 0 on success, -1 if transport is NULL
*/
int transport_free(struct transport *transport)
{
if (transport == NULL) {
return -1;
}
if (transport->fd != -1) {
close(transport->fd);
transport->fd = -1;
}
return 0;
}
================================================
FILE: src/utf8.c
================================================
/*
* utf8.c
* UTF-8 utilities
* Author: Michael Czigler
* License: MIT
*/
#include "utf8.h"
/**
* utf8_prev_char_len() - Get byte length of previous UTF-8 character
* @s: UTF-8 string
* @pos: Current byte position in string
*
* Calculates the byte length of the UTF-8 character immediately before
* the given position by walking backward to find the start byte. Handles
* multi-byte sequences by identifying continuation bytes (0x80-0xBF).
*
* Return: Number of bytes in previous character, or 0 if at start
*/
int utf8_prev_char_len(const char *s, int pos)
{
if (pos <= 0) {
return 0;
}
/* Walk back to find start of UTF-8 character */
int prev = pos - 1;
while (prev > 0 && ((unsigned char)s[prev] & 0xC0) == 0x80) {
prev--;
}
return pos - prev;
}
/**
* utf8_next_char_len() - Get byte length of next UTF-8 character
* @s: UTF-8 string
* @pos: Current byte position in string
* @maxlen: Maximum valid position in string
*
* Determines the byte length of the UTF-8 character at the given position
* using mbrtowc(). Handles invalid sequences by treating them as single
* bytes. Safely handles null characters and position boundaries.
*
* Return: Number of bytes in next character (1-4), or 0 if at end
*/
int utf8_next_char_len(const char *s, int pos, int maxlen)
{
if (pos >= maxlen) {
return 0;
}
mbstate_t st;
memset(&st, 0, sizeof(st));
wchar_t wc;
size_t ret = mbrtowc(&wc, s + pos, maxlen - pos, &st);
if (ret == (size_t)-1 || ret == (size_t)-2) {
/* Invalid UTF-8 sequence, treat as single byte */
return 1;
}
if (ret == 0) {
/* Null character */
return 1;
}
return (int)ret;
}
/**
* utf8_validate() - Validate UTF-8 encoding of string
* @s: String to validate
* @len: Number of bytes to validate
*
* Checks whether a byte sequence is valid UTF-8 by attempting to decode
* each character using mbrtowc(). Rejects strings with invalid sequences,
* incomplete multi-byte sequences, or other encoding errors.
*
* Return: 1 if valid UTF-8, 0 if invalid or incomplete
*/
int utf8_validate(const char *s, int len)
{
mbstate_t st;
memset(&st, 0, sizeof(st));
int pos = 0;
while (pos < len) {
wchar_t wc;
size_t ret = mbrtowc(&wc, s + pos, len - pos, &st);
if (ret == (size_t)-1) {
/* Invalid sequence */
return 0;
}
if (ret == (size_t)-2) {
/* Incomplete sequence at end */
return 0;
}
if (ret == 0) {
/* Null encountered */
break;
}
pos += ret;
}
return 1;
}
gitextract_cdfbzy5t/
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── config.mk
├── include/
│ ├── ansi.h
│ ├── base64.h
│ ├── config.h
│ ├── ctcp.h
│ ├── dcc.h
│ ├── editor.h
│ ├── event.h
│ ├── handler.h
│ ├── helper.h
│ ├── kirc.h
│ ├── network.h
│ ├── output.h
│ ├── protocol.h
│ ├── terminal.h
│ ├── transport.h
│ └── utf8.h
├── kirc.1
└── src/
├── base64.c
├── config.c
├── ctcp.c
├── dcc.c
├── editor.c
├── event.c
├── handler.c
├── helper.c
├── main.c
├── network.c
├── output.c
├── protocol.c
├── terminal.c
├── transport.c
└── utf8.c
SYMBOL INDEX (230 symbols across 27 files)
FILE: include/config.h
type kirc_context (line 15) | struct kirc_context
type kirc_context (line 16) | struct kirc_context
type kirc_context (line 17) | struct kirc_context
FILE: include/ctcp.h
type network (line 16) | struct network
type event (line 16) | struct event
type output (line 16) | struct output
type network (line 17) | struct network
type event (line 17) | struct event
type output (line 17) | struct output
type network (line 18) | struct network
type event (line 18) | struct event
type output (line 18) | struct output
type network (line 19) | struct network
type event (line 19) | struct event
type output (line 19) | struct output
FILE: include/dcc.h
type dcc_type (line 18) | enum dcc_type {
type dcc_state (line 23) | enum dcc_state {
type dcc_transfer (line 31) | struct dcc_transfer {
type dcc (line 41) | struct dcc {
type dcc (line 48) | struct dcc
type kirc_context (line 48) | struct kirc_context
type dcc (line 49) | struct dcc
type dcc (line 50) | struct dcc
type dcc (line 52) | struct dcc
type dcc (line 53) | struct dcc
type dcc (line 54) | struct dcc
type dcc (line 55) | struct dcc
type network (line 55) | struct network
type event (line 56) | struct event
FILE: include/editor.h
type editor_state (line 16) | enum editor_state {
type editor (line 22) | struct editor {
type editor (line 33) | struct editor
type editor (line 34) | struct editor
type kirc_context (line 34) | struct kirc_context
type editor (line 35) | struct editor
type editor (line 36) | struct editor
FILE: include/event.h
type event_type (line 14) | enum event_type {
type event_dispatch_table (line 196) | struct event_dispatch_table {
type event (line 201) | struct event {
type event (line 212) | struct event
type kirc_context (line 212) | struct kirc_context
type event (line 213) | struct event
FILE: include/handler.h
type network (line 16) | struct network
type event (line 16) | struct event
type output (line 16) | struct output
type handler (line 18) | struct handler {
type handler (line 24) | struct handler
type handler (line 25) | struct handler
type event_type (line 25) | enum event_type
type handler (line 26) | struct handler
type network (line 26) | struct network
type event (line 26) | struct event
type output (line 26) | struct output
type handler (line 28) | struct handler
type kirc_context (line 28) | struct kirc_context
FILE: include/kirc.h
type sasl_mechanism (line 66) | enum sasl_mechanism {
type kirc_context (line 72) | struct kirc_context {
FILE: include/network.h
type network (line 17) | struct network {
type network (line 24) | struct network
type network (line 25) | struct network
type network (line 26) | struct network
type network (line 27) | struct network
type output (line 27) | struct output
type network (line 28) | struct network
type network (line 30) | struct network
type transport (line 31) | struct transport
type kirc_context (line 31) | struct kirc_context
type network (line 32) | struct network
FILE: include/output.h
type output (line 14) | struct output {
type output (line 20) | struct output
type kirc_context (line 21) | struct kirc_context
type output (line 23) | struct output
type output (line 26) | struct output
type output (line 27) | struct output
type output (line 29) | struct output
FILE: include/protocol.h
type network (line 18) | struct network
type event (line 18) | struct event
type output (line 18) | struct output
type network (line 19) | struct network
type event (line 19) | struct event
type output (line 19) | struct output
type network (line 20) | struct network
type event (line 20) | struct event
type output (line 20) | struct output
type network (line 21) | struct network
type event (line 21) | struct event
type output (line 21) | struct output
type network (line 22) | struct network
type event (line 22) | struct event
type output (line 22) | struct output
type network (line 23) | struct network
type event (line 23) | struct event
type output (line 23) | struct output
type network (line 24) | struct network
type event (line 24) | struct event
type output (line 24) | struct output
type network (line 25) | struct network
type event (line 25) | struct event
type output (line 25) | struct output
type network (line 26) | struct network
type event (line 26) | struct event
type output (line 26) | struct output
type network (line 27) | struct network
type event (line 27) | struct event
type output (line 27) | struct output
type network (line 28) | struct network
type event (line 28) | struct event
type output (line 28) | struct output
type network (line 29) | struct network
type event (line 29) | struct event
type output (line 29) | struct output
type network (line 30) | struct network
type event (line 30) | struct event
type output (line 30) | struct output
type network (line 31) | struct network
type event (line 31) | struct event
type output (line 31) | struct output
FILE: include/terminal.h
type terminal (line 14) | struct terminal {
type terminal (line 21) | struct terminal
type kirc_context (line 22) | struct kirc_context
type terminal (line 23) | struct terminal
type terminal (line 24) | struct terminal
FILE: include/transport.h
type transport (line 13) | struct transport {
type transport (line 18) | struct transport
type transport (line 20) | struct transport
type transport (line 23) | struct transport
type transport (line 24) | struct transport
type kirc_context (line 25) | struct kirc_context
type transport (line 26) | struct transport
FILE: src/base64.c
function base64_encode (line 23) | int base64_encode(char *out, const char *in, size_t in_len)
FILE: src/config.c
function config_validate_port (line 19) | static int config_validate_port(const char *value)
function config_parse_channels (line 61) | static void config_parse_channels(struct kirc_context *ctx, char *value)
function config_parse_mechanism (line 82) | static void config_parse_mechanism(struct kirc_context *ctx, char *value)
function config_apply_env (line 169) | static int config_apply_env(struct kirc_context *ctx, const char *env_name,
function config_init (line 190) | int config_init(struct kirc_context *ctx)
function config_parse_args (line 250) | int config_parse_args(struct kirc_context *ctx, int argc, char *argv[])
function config_free (line 326) | int config_free(struct kirc_context *ctx)
FILE: src/ctcp.c
function ctcp_handle_clientinfo (line 20) | void ctcp_handle_clientinfo(struct network *network, struct event *event...
function ctcp_handle_ping (line 42) | void ctcp_handle_ping(struct network *network, struct event *event, stru...
function ctcp_handle_time (line 71) | void ctcp_handle_time(struct network *network, struct event *event, stru...
function ctcp_handle_version (line 98) | void ctcp_handle_version(struct network *network, struct event *event, s...
FILE: src/dcc.c
function sanitize_filename (line 20) | static int sanitize_filename(char *filename)
function dcc_init (line 60) | int dcc_init(struct dcc *dcc, struct kirc_context *ctx)
function dcc_free (line 90) | int dcc_free(struct dcc *dcc)
function dcc_send (line 124) | int dcc_send(struct dcc *dcc, int transfer_id)
function dcc_process (line 199) | int dcc_process(struct dcc *dcc)
function dcc_request (line 348) | int dcc_request(struct dcc *dcc, const char *sender, const char *params)
function dcc_cancel (line 541) | int dcc_cancel(struct dcc *dcc, int transfer_id)
function dcc_handle (line 582) | void dcc_handle(struct dcc *dcc, struct network *network, struct event *...
FILE: src/editor.c
function editor_backspace (line 18) | static void editor_backspace(struct editor *editor)
function editor_delete_line (line 48) | static void editor_delete_line(struct editor *editor)
function editor_enter (line 63) | static int editor_enter(struct editor *editor)
function editor_delete (line 93) | static void editor_delete(struct editor *editor)
function editor_history (line 123) | static void editor_history(struct editor *editor, int dir)
function editor_move_right (line 180) | static void editor_move_right(struct editor *editor)
function editor_move_left (line 207) | static void editor_move_left(struct editor *editor)
function editor_move_home (line 227) | static void editor_move_home(struct editor *editor)
function editor_move_end (line 238) | static void editor_move_end(struct editor *editor)
function editor_escape (line 254) | static void editor_escape(struct editor *editor)
function editor_insert (line 314) | static void editor_insert(struct editor *editor, char c)
function editor_insert_bytes (line 345) | static void editor_insert_bytes(struct editor *editor, const char *buf, ...
function editor_clear (line 378) | static void editor_clear(struct editor *editor)
function display_width_bytes (line 394) | static int display_width_bytes(const char *s, int bytes)
function editor_tab (line 431) | static void editor_tab(struct editor *editor)
type editor (line 448) | struct editor
function editor_init (line 467) | int editor_init(struct editor *editor, struct kirc_context *ctx)
function editor_process_key (line 494) | int editor_process_key(struct editor *editor)
function editor_handle (line 603) | int editor_handle(struct editor *editor)
FILE: src/event.c
type event_dispatch_table (line 11) | struct event_dispatch_table
function event_init (line 195) | int event_init(struct event *event, struct kirc_context *ctx)
function event_ctcp_parse (line 216) | static int event_ctcp_parse(struct event *event)
function event_parse (line 306) | int event_parse(struct event *event, char *line)
FILE: src/handler.c
function handler_default (line 18) | void handler_default(struct handler *handler,
function handler_register (line 34) | void handler_register(struct handler *handler, enum event_type type,
function handler_init (line 54) | int handler_init(struct handler *handler, struct kirc_context *ctx)
function handler_dispatch (line 78) | void handler_dispatch(struct handler *handler, struct network *network,
FILE: src/helper.c
function safecpy (line 22) | int safecpy(char *s1, const char *s2, size_t n)
function memzero (line 45) | int memzero(void *s, size_t n)
FILE: src/main.c
function kirc_register_handlers (line 29) | static void kirc_register_handlers(struct handler *handler) {
function kirc_run (line 222) | static int kirc_run(struct kirc_context *ctx)
function main (line 424) | int main(int argc, char *argv[])
FILE: src/network.c
function network_send (line 21) | int network_send(struct network *network, const char *fmt, ...)
function network_receive (line 60) | int network_receive(struct network *network)
function network_connect (line 95) | int network_connect(struct network *network)
function network_send_private_msg (line 110) | static void network_send_private_msg(struct network *network,
function network_send_topic (line 157) | static void network_send_topic(struct network *network,
function network_send_ctcp_action (line 190) | static void network_send_ctcp_action(struct network *network,
function network_send_ctcp_command (line 224) | static void network_send_ctcp_command(struct network *network,
function network_send_channel_msg (line 261) | static void network_send_channel_msg(
function network_command_handler (line 299) | int network_command_handler(struct network *network, char *msg, struct o...
function network_send_credentials (line 366) | int network_send_credentials(struct network *network)
function network_init (line 422) | int network_init(struct network *network,
function network_free (line 446) | int network_free(struct network *network)
FILE: src/output.c
function output_init (line 20) | int output_init(struct output *output,
function output_append (line 49) | int output_append(struct output *output,
function output_flush (line 104) | void output_flush(struct output *output)
function output_clear (line 127) | void output_clear(struct output *output)
function output_pending (line 146) | int output_pending(struct output *output)
FILE: src/protocol.c
type tm (line 24) | struct tm
function protocol_noop (line 39) | void protocol_noop(struct network *network, struct event *event, struct ...
function protocol_ping (line 55) | void protocol_ping(struct network *network, struct event *event, struct ...
function network_authenticate_plain (line 69) | static void network_authenticate_plain(struct network *network)
function protocol_authenticate (line 94) | void protocol_authenticate(struct network *network, struct event *event,...
function protocol_welcome (line 132) | void protocol_welcome(struct network *network, struct event *event, stru...
function protocol_raw (line 152) | void protocol_raw(struct network *network, struct event *event, struct o...
function protocol_info (line 170) | void protocol_info(struct network *network, struct event *event, struct ...
function protocol_error (line 187) | void protocol_error(struct network *network, struct event *event, struct...
function protocol_notice (line 205) | void protocol_notice(struct network *network, struct event *event, struc...
function protocol_privmsg_direct (line 223) | static void protocol_privmsg_direct(struct network *network, struct even...
function protocol_privmsg_indirect (line 241) | static void protocol_privmsg_indirect(struct network *network, struct ev...
function protocol_privmsg (line 261) | void protocol_privmsg(struct network *network, struct event *event, stru...
function protocol_nick (line 283) | void protocol_nick(struct network *network, struct event *event, struct ...
function protocol_join (line 314) | void protocol_join(struct network *network, struct event *event, struct ...
function protocol_part (line 336) | void protocol_part(struct network *network, struct event *event, struct ...
function protocol_ctcp_action (line 358) | void protocol_ctcp_action(struct network *network, struct event *event, ...
function protocol_ctcp_info (line 376) | void protocol_ctcp_info(struct network *network, struct event *event, st...
FILE: src/terminal.c
function terminal_get_cursor_column (line 20) | static int terminal_get_cursor_column(int in_fd, int out_fd)
function terminal_columns (line 64) | int terminal_columns(int tty_fd)
function terminal_enable_raw (line 118) | int terminal_enable_raw(struct terminal *terminal)
function terminal_disable_raw (line 159) | void terminal_disable_raw(struct terminal *terminal)
function terminal_init (line 179) | int terminal_init(struct terminal *terminal,
FILE: src/transport.c
function poll_wait_write (line 20) | static int poll_wait_write(int fd, int timeout_ms)
function transport_send (line 49) | ssize_t transport_send(struct transport *transport,
function transport_receive (line 79) | ssize_t transport_receive(struct transport *transport,
function transport_connect (line 118) | int transport_connect(struct transport *transport)
function transport_init (line 205) | int transport_init(struct transport *transport,
function transport_free (line 229) | int transport_free(struct transport *transport)
FILE: src/utf8.c
function utf8_prev_char_len (line 21) | int utf8_prev_char_len(const char *s, int pos)
function utf8_next_char_len (line 48) | int utf8_next_char_len(const char *s, int pos, int maxlen)
function utf8_validate (line 84) | int utf8_validate(const char *s, int len)
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (176K chars).
[
{
"path": ".gitignore",
"chars": 12,
"preview": "kirc\n*.a\n*.o"
},
{
"path": "LICENSE",
"chars": 1077,
"preview": "MIT License\n\nCopyright (c) 2021-2025 Michael Czigler\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "Makefile",
"chars": 933,
"preview": ".POSIX:\n.SUFFIXES:\n\ninclude config.mk\n\nCFLAGS += -std=c99 -pedantic -Wall -Wextra\nCFLAGS += -Wformat-security -Wwrite-st"
},
{
"path": "README.md",
"chars": 974,
"preview": "kirc\n====\n\nkirc is a tiny, dependency-light IRC client that follows the\n\"Keep It Simple, Stupid\" (KISS) philosophy. It i"
},
{
"path": "config.mk",
"chars": 124,
"preview": "# config.mk - default build configuration\n\nPREFIX = /usr/local\nBINDIR = $(PREFIX)/bin\nMANDIR = $(PREFIX)/share/man\n\nCC ="
},
{
"path": "include/ansi.h",
"chars": 1643,
"preview": "/*\n * ansi.h\n * ANSI escape codes and constants\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_ANSI_H\n#d"
},
{
"path": "include/base64.h",
"chars": 256,
"preview": "/*\n * base64.h\n * Header for base64 encoding module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_BASE6"
},
{
"path": "include/config.h",
"chars": 393,
"preview": "/*\n * config.h\n * Header for configuration module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_CONFIG_"
},
{
"path": "include/ctcp.h",
"chars": 618,
"preview": "/*\n * ctcp.h\n * Header for the CTCP module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_CTCP_H\n#define"
},
{
"path": "include/dcc.h",
"chars": 1270,
"preview": "/*\n * dcc.h\n * Header for the DCC module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_DCC_H\n#define __"
},
{
"path": "include/editor.h",
"chars": 851,
"preview": "/*\n * editor.h\n * Header for the editor module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_EDITOR_H\n#"
},
{
"path": "include/event.h",
"chars": 5913,
"preview": "/*\n * event.h\n * Header for the event module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_EVENT_H\n#def"
},
{
"path": "include/handler.h",
"chars": 868,
"preview": "/*\n * handler.h\n * Header for handler dispatch module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_HAN"
},
{
"path": "include/helper.h",
"chars": 329,
"preview": "/*\n * helper.h\n * Header for the helper module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_HELPER_H\n#"
},
{
"path": "include/kirc.h",
"chars": 1992,
"preview": "/*\n * kirc.h\n * Main header file for kirc IRC client\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_H\n#d"
},
{
"path": "include/network.h",
"chars": 858,
"preview": "/*\n * network.h\n * Header for the network module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_NETWORK_"
},
{
"path": "include/output.h",
"chars": 599,
"preview": "/*\n * output.h\n * Header for the buffered output module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_O"
},
{
"path": "include/protocol.h",
"chars": 1573,
"preview": "/*\n * protocol.h\n * Header for the protocol module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_PROTOC"
},
{
"path": "include/terminal.h",
"chars": 546,
"preview": "/*\n * terminal.h\n * Header for the terminal module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_TERMIN"
},
{
"path": "include/transport.h",
"chars": 639,
"preview": "/*\n * transport.h\n * Header for the transport module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_TRAN"
},
{
"path": "include/utf8.h",
"chars": 333,
"preview": "/*\n * utf8.h\n * Header for the UTF-8 module\n * Author: Michael Czigler\n * License: MIT\n */\n\n#ifndef __KIRC_UTF8_H\n#defin"
},
{
"path": "kirc.1",
"chars": 14091,
"preview": ".TH KIRC 1 \"December 2025\"\n.SH NAME\nkirc \\- a tiny, dependency-light IRC client\n.SH SYNOPSIS\n.B kirc\n.RB [\\-s \" server\"]"
},
{
"path": "src/base64.c",
"chars": 1511,
"preview": "/*\n * base64.c\n * Base64 encoding handlers\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"base64.h\"\n\n/**\n * b"
},
{
"path": "src/config.c",
"chars": 9130,
"preview": "/*\n * config.c\n * Configuration parsing and initialization\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"con"
},
{
"path": "src/ctcp.c",
"chars": 3406,
"preview": "/*\n * ctcp.c\n * Client-to-client protocol (CTCP) event handling\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include"
},
{
"path": "src/dcc.c",
"chars": 17390,
"preview": "/*\n * dcc.c\n * Direct client-to-client (DCC) event handling\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"dc"
},
{
"path": "src/editor.c",
"chars": 17411,
"preview": "/*\n * editor.c\n * Text editor functionality for the IRC client\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include "
},
{
"path": "src/event.c",
"chars": 14024,
"preview": "/*\n * event.c\n * Message event handling\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"event.h\"\n\n/* Event dis"
},
{
"path": "src/handler.c",
"chars": 2910,
"preview": "/*\n * handler.c\n * Unified event handler dispatch system\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"handl"
},
{
"path": "src/helper.c",
"chars": 2362,
"preview": "/*\n * helper.c\n * Helper functions\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"helper.h\"\n\n/**\n * safecpy()"
},
{
"path": "src/main.c",
"chars": 19716,
"preview": "/*\n * main.c\n * Main entry point for the kirc IRC client\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"confi"
},
{
"path": "src/network.c",
"chars": 13781,
"preview": "/*\n * network.c\n * Network connection management\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"network.h\"\n\n/"
},
{
"path": "src/output.c",
"chars": 3664,
"preview": "/*\n * output.c\n * Buffered output for improved terminal responsiveness\n * Author: Michael Czigler\n * License: MIT\n */\n\n#"
},
{
"path": "src/protocol.c",
"chars": 12870,
"preview": "/*\n * protocol.c\n * IRC protocol handling\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"protocol.h\"\n\n/**\n * "
},
{
"path": "src/terminal.c",
"chars": 4592,
"preview": "/*\n * terminal.c\n * Terminal interface and display management\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \""
},
{
"path": "src/transport.c",
"chars": 5781,
"preview": "/*\n * transport.c\n * Data transport layer\n * Author: Michael Czigler\n * License: MIT\n */\n\n#include \"transport.h\"\n\n/**\n *"
},
{
"path": "src/utf8.c",
"chars": 2767,
"preview": "/*\n * utf8.c\n * UTF-8 utilities\n * Author: Michael Czigler\n * License: MIT\n */\n \n#include \"utf8.h\"\n\n/**\n * utf8_prev_cha"
}
]
About this extraction
This page contains the full source code of the mcpcpc/kirc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (163.3 KB), approximately 43.5k tokens, and a symbol index with 230 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.