~rcr/rirc

d9cde15fb00dd9e51682228282645b8a48c2c327 — Richard Robbins 9 months ago 78453c9 + e54533b v0.1.4
Merge branch 'static_analysis'
68 files changed, 3230 insertions(+), 2001 deletions(-)

M .builds/alpine.yml
M .builds/debian.yml
M .gitignore
D CHANGELOG
M LICENSE
M Makefile
M README.md
M config.def.h
M lib/mbedtls
M rirc.1
M scripts/compile_commands.sh
M scripts/coverage.sh
M scripts/pre-commit.sh
R scripts/{coverity_get.sh => sa_coverity_get.sh}
R scripts/{coverity_run.sh => sa_coverity_run.sh}
A scripts/sa_sonarcloud_get.sh
A scripts/sa_sonarcloud_run.sh
M scripts/sanitizers_build.sh
M scripts/sanitizers_test.sh
M src/components/buffer.c
M src/components/buffer.h
M src/components/channel.c
M src/components/channel.h
M src/components/input.c
M src/components/input.h
M src/components/ircv3.c
M src/components/ircv3.h
M src/components/mode.c
M src/components/mode.h
M src/components/server.c
M src/components/server.h
M src/components/user.c
M src/components/user.h
M src/draw.c
M src/draw.h
M src/handlers/irc_ctcp.c
M src/handlers/irc_ctcp.h
M src/handlers/irc_recv.c
M src/handlers/irc_recv.gperf
M src/handlers/irc_recv.h
M src/handlers/irc_send.c
M src/handlers/irc_send.gperf
M src/handlers/irc_send.h
M src/handlers/ircv3.c
M src/handlers/ircv3.h
M src/io.c
M src/io.h
M src/rirc.c
M src/rirc.h
M src/state.c
M src/state.h
M src/utils/list.h
M src/utils/tree.h
M src/utils/utils.c
M src/utils/utils.h
M test/components/channel.c
M test/components/mode.c
M test/components/server.c
M test/components/user.c
M test/handlers/irc_ctcp.c
M test/handlers/irc_recv.c
M test/handlers/irc_send.c
M test/handlers/ircv3.c
M test/io.mock.c
M test/state.c
M test/state.mock.c
M test/utils/tree.c
M test/utils/utils.c
M .builds/alpine.yml => .builds/alpine.yml +9 -1
@@ 13,6 13,14 @@ tasks:
      cd rirc
      git submodule init
      git submodule update --recursive
      export MAKEFLAGS='-j $(nproc)'
  - build: |
      cd rirc
      make rirc rirc.debug test
      make clean
      make all
      make check

triggers:
  - action: email
    condition: failure
    to: mail+sourcehut+builds@rcr.io

M .builds/debian.yml => .builds/debian.yml +14 -28
@@ 10,9 10,6 @@ packages:
sources:
  - https://git.sr.ht/~rcr/rirc

environment:
  SONAR_VER: 4.4.0.2170

secrets:
  - 8c2439c9-5f91-4b19-b3c3-33d82f1b861f
  - a58d0951-f57f-44ef-8ef2-25f2f84f0e89


@@ 22,36 19,25 @@ tasks:
      cd rirc
      git submodule init
      git submodule update --recursive
      export MAKEFLAGS='-j $(nproc)'
  - build: |
      cd rirc
      make rirc rirc.debug test
      make clean
      make all
      make check
  - static-analysis: |
      cd rirc
      branch=$(git name-rev --name-only HEAD)
      [ $branch = "remotes/origin/static_analysis" ] || complete-build
      [ $(git name-rev --name-only HEAD) = "remotes/origin/static_analysis" ] || complete-build
      set +x
      source ~/export_coverity
      source ~/export_sonarscan
      set -x
      # Coverity
      ./scripts/coverity_get.sh coverity
      ./scripts/coverity_run.sh coverity
      # Sonarcloud
      curl -o build-wrapper.zip https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip
      curl -o sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip
      unzip build-wrapper.zip
      unzip sonar-scanner.zip
      ./scripts/coverage.sh
      echo >> sonar-project.properties "sonar.branch.name=$branch"
      echo >> sonar-project.properties "sonar.cfamily.build-wrapper-output=bw-output"
      echo >> sonar-project.properties "sonar.cfamily.cache.enabled=false"
      echo >> sonar-project.properties "sonar.cfamily.gcov.reportsPath=."
      echo >> sonar-project.properties "sonar.cfamily.threads=1"
      echo >> sonar-project.properties "sonar.coverage.exclusions=test"
      echo >> sonar-project.properties "sonar.host.url=https://sonarcloud.io"
      echo >> sonar-project.properties "sonar.organization=rirc"
      echo >> sonar-project.properties "sonar.projectKey=rirc"
      echo >> sonar-project.properties "sonar.sources=src"
      echo >> sonar-project.properties "sonar.tests=test"
      ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw-output make clean rirc.debug test
      ./sonar-scanner-$SONAR_VER-linux/bin/sonar-scanner
      ./scripts/sa_coverity_get.sh coverity
      ./scripts/sa_coverity_run.sh coverity
      ./scripts/sa_sonarcloud_get.sh sonarcloud
      ./scripts/sa_sonarcloud_run.sh sonarcloud

triggers:
  - action: email
    condition: failure
    to: mail+sourcehut+builds@rcr.io

M .gitignore => .gitignore +1 -0
@@ 6,6 6,7 @@
*.t
*.td
.clangd
.cache
bld
compile_commands.json
config.h

D CHANGELOG => CHANGELOG +0 -88
@@ 1,88 0,0 @@
# Changelog
Summary of notable changes and features

## Unreleased (dev)

## [0.1.3]
### Features
 - add command :disconnect
 - add cli option `--ipv4`
 - add cli option `--ipv6`
 - add cli option `--tls-disable`
 - add cli option `--tls-verify`
 - add TLS support
    - add mbedtls git submodule
    - add CA_CERT_PATH define to config.h
    - changed default connection type to TLS
    - changed default port to 6697
 - add IRCv3 CAP support
    - add command /cap-ls
    - add command /cap-list
    - add IRCv3 CAP multi-prefix
 - changed standard versions
    - c99          -> c11
    - POSIX.1-2001 -> POSIX.1-2008
### Fixes
 - fix segfault in CTCP extended format handling
 - fix default username/realname not being set

## [0.1.2]
### Features
 - add 116 previously unhandled numerics:
    - 200, 201, 202, 203, 204, 205, 206, 207,
    - 208, 209, 210, 211, 212, 213, 214, 215,
    - 216, 217, 218, 219, 221, 234, 235, 240,
    - 241, 242, 243, 244, 245, 246, 247, 256,
    - 257, 258, 259, 262, 263, 301, 302, 303,
    - 305, 306, 311, 312, 313, 314, 315, 317,
    - 318, 319, 322, 323, 324, 325, 329, 341,
    - 346, 347, 348, 349, 351, 352, 364, 365,
    - 367, 368, 369, 371, 374, 381, 391, 405,
    - 406, 407, 408, 409, 411, 412, 413, 414,
    - 415, 416, 421, 422, 423, 431, 436, 437,
    - 441, 442, 443, 451, 461, 462, 463, 464,
    - 465, 466, 467, 471, 472, 474, 475, 476,
    - 478, 481, 482, 483, 484, 485, 491, 501,
    - 502, 704, 705, 706,
 - add CTCP FINGER
 - add CTCP SOURCE
 - add CTCP USERINFO
 - add INVITE
 - add CASEMAPPING handling
### Refactor
 - generate gperf files at compile time
 - split mesg.c into:
    - handlers/irc_send.c
    - handlers/irc_recv.c
    - handlers/irc_ctcp.c
 - add unit test file for each source file
### Fixes
 - fix JOIN/PART/QUIT filtering bug when set 0
 - fix nav colouring bug when message received on current chan
 - fix handling of CTCP PING
 - fix segfault on unrecognized cli args

## [0.1.1]
### Features
 - tab completion for :commands
### Refactor
 - input module complete rewrite
   - generic word completion API
   - add full test coverage
   - performance and memory usage improvements
   - tab completion as generic callbacks
### Fixes
 - fix error in sending PING
 - fix error messages on socket disconnect
 - fix fatal error on /quit
 - close/cleanup servers on :quit
 - print message on channel re-join
 - fix recursive exit() calls on fatal

## [0.1.0]
### Initial release
Started in 2013 as a hobby project, rirc has been a fully useable,
albeit basic, IRC client since it's early inception. This version
marks an initial release supporting a basic subset of the IRC
protocol and client management features one would expect in everyday
usage.

M LICENSE => LICENSE +1 -1
@@ 1,4 1,4 @@
Copyright (C) 2014-2020 Richard Robbins <mail@rcr.io>
Copyright (C) 2014-2021 Richard Robbins <mail@rcr.io>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

M Makefile => Makefile +38 -29
@@ 1,4 1,4 @@
VERSION := 0.1.3
VERSION := 0.1.4

# Release and debug build executable names
BIN_R := rirc


@@ 28,6 28,8 @@ DIR_B := bld
DIR_S := src
DIR_T := test

PWD  := $(shell pwd)

SRC     := $(shell find $(DIR_S) -name '*.c')
SUBDIRS += $(shell find $(DIR_S) -name '*.c' -exec dirname {} \; | sort -u)



@@ 44,53 46,62 @@ OBJS_T += $(DIR_B)/utils/tree.t # Header only file
OBJS_G := $(patsubst %.gperf, %.gperf.out, $(SRC_G))

# Release build executable
$(BIN_R): $(TLS_LIBS) $(DIR_B) $(OBJS_G) $(OBJS_R)
	@echo cc $@
$(BIN_R): $(TLS_LIBS) $(OBJS_G) $(OBJS_R)
	@echo " CC  $@"
	@$(CC) $(LDFLAGS) -o $@ $(OBJS_R) $(TLS_LIBS)

# Debug build executable
$(BIN_D): $(TLS_LIBS) $(DIR_B) $(OBJS_G) $(OBJS_D)
	@echo cc $@
$(BIN_D): $(TLS_LIBS) $(OBJS_G) $(OBJS_D)
	@echo " CC  $@"
	@$(CC) $(LDFLAGS) -o $@ $(OBJS_D) $(TLS_LIBS)

# Release build objects
$(DIR_B)/%.o: $(DIR_S)/%.c config.h
	@echo "cc $<..."
	@$(PP) $(CFLAGS_R) -MM -MP -MT $@ -MF $(@:.o=.d) $<
$(DIR_B)/%.o: $(DIR_S)/%.c config.h | $(DIR_B)
	@echo " CC  $<"
	@$(PP) $(CFLAGS_R) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
	@$(CC) $(CFLAGS_R) -c -o $@ $<

# Debug build objects
$(DIR_B)/%.db.o: $(DIR_S)/%.c config.h
	@echo "cc $<..."
	@$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.o=.d) $<
$(DIR_B)/%.db.o: $(DIR_S)/%.c config.h | $(DIR_B)
	@echo " CC  $<"
	@$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
	@$(CC) $(CFLAGS_D) -c -o $@ $<

# Default config file
# Testcases
$(DIR_B)/%.t: $(DIR_T)/%.c $(OBJS_G) | $(DIR_B)
	@$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.t=.t.d) $<
	@$(CC) $(CFLAGS_D) -c -o $(@:.t=.t.o) $<
	@$(CC) $(CFLAGS_D) -o $@ $(@:.t=.t.o)
	@$(TEST_EXT) ./$@ || mv $@ $(@:.t=.td)
	@[ -f $@ ]

config.h:
	cp config.def.h config.h

# Gperf generated source
%.gperf.out: %.gperf
	gperf --output-file=$@ $<

# Testcase files
$(DIR_B)/%.t: $(DIR_T)/%.c
	@$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.t=.d) $<
	@$(CC) $(CFLAGS_D) $(LDFLAGS) -o $@ $<
	-@rm -f $(@:.t=.td) && $(TEST_EXT) ./$@ || mv $@ $(@:.t=.td)
	@[ ! -f $(@:.t=.td) ]

# Build directories
$(DIR_B):
	@for dir in $(patsubst $(DIR_S)/%, %, $(SUBDIRS)); do mkdir -p $(DIR_B)/$$dir; done

# TLS libraries
$(TLS_LIBS): $(TLS_CONF)
	@CFLAGS="$(TLS_INCL)" $(MAKE) -C ./lib/mbedtls clean lib
	@CFLAGS="$(TLS_INCL)" $(MAKE) --silent -C ./lib/mbedtls clean
	@CFLAGS="$(TLS_INCL)" $(MAKE) --silent -C ./lib/mbedtls lib

all:
	@$(MAKE) --silent $(BIN_R)
	@$(MAKE) --silent $(BIN_D)

check:
	@$(MAKE) --silent $(OBJS_T)

clean:
	rm -rf $(DIR_B) $(BIN_R) $(BIN_D)
	find . -name "*gperf.out" -print0 | xargs -0 -I % rm %
	@rm -rf $(DIR_B)
	@rm -vf $(BIN_R) $(BIN_D) $(OBJS_G)

libs: $(TLS_LIBS)

install: $(BIN_R)
	@echo installing executable to $(BIN_DIR)


@@ 105,10 116,8 @@ uninstall:
	rm -f $(BIN_DIR)/rirc
	rm -f $(MAN_DIR)/rirc.1

test: $(DIR_B) $(OBJS_G) $(OBJS_T)

-include $(OBJS_R:.o=.d)
-include $(OBJS_D:.o=.d)
-include $(OBJS_T:.t=.d)
-include $(OBJS_R:.o=.o.d)
-include $(OBJS_D:.o=.o.d)
-include $(OBJS_T:.t=.t.d)

.PHONY: clean install uninstall test
.PHONY: all check clean libs install uninstall

M README.md => README.md +2 -3
@@ 93,7 93,7 @@ Commands:
```
  :clear
  :close
  :connect [host [port] [pass] [user] [real]]
  :connect
  :disconnect
  :quit
```


@@ 105,7 105,6 @@ Keys:
  ^P : go to previous channel
  ^L : clear channel
  ^X : close channel
  ^F : find channel
  ^C : cancel input/action
  ^U : scroll buffer up
  ^D : scroll buffer down


@@ 117,4 116,4 @@ Keys:

### More info:

[rcr.io/rirc/](http://rcr.io/rirc/)
[https://rcr.io/rirc/](http://rcr.io/rirc/)

M config.def.h => config.def.h +12 -5
@@ 16,12 16,15 @@
#define DEFAULT_USERNAME ""
#define DEFAULT_REALNAME ""

/* User count in channel before filtering JOIN/PART/QUIT messages
/* User count in channel before filtering message types
 *   Integer
 *   (0: no filtering) */
#define JOIN_THRESHOLD 0
#define PART_THRESHOLD 0
#define QUIT_THRESHOLD 0
#define FILTER_THRESHOLD_JOIN    0
#define FILTER_THRESHOLD_PART    0
#define FILTER_THRESHOLD_QUIT    0
#define FILTER_THRESHOLD_ACCOUNT 0
#define FILTER_THRESHOLD_AWAY    0
#define FILTER_THRESHOLD_CHGHOST 0

/* Message sent for PART and QUIT by default */
#define DEFAULT_QUIT_MESG "rirc v" VERSION


@@ 61,12 64,16 @@
#define INPUT_PREFIX_FG 239
#define INPUT_PREFIX_BG -1

/* Action message */
#define ACTION_FG -1
#define ACTION_BG 239

/* Input line text colours */
#define INPUT_FG 250
#define INPUT_BG -1

/* BUFFER_PADDING:
 * How the buffer line headers will be padded, options are 0, 1
 * How the buffer line headers will be padded [0, 1]
 *
 * 0 (Unpadded):
 *   12:34 alice ~ hello

M lib/mbedtls => lib/mbedtls +1 -1
@@ 1,1 1,1 @@
Subproject commit 523f0554b6cdc7ace5d360885c3f5bbcc73ec0e8
Subproject commit 1c54b5410fd48d6bcada97e30cac417c5c7eea67

M rirc.1 => rirc.1 +1 -2
@@ 68,7 68,6 @@ rirc is controlled by a combination of key bindings and commands, where:
.tab(;);
lb l .
Keys:
  ^F;find channel
  ^N;go to next channel
  ^P;go to previous channel
  ^C;cancel current input/action


@@ 94,7 93,7 @@ lb l .
Commands:
  :clear;
  :close;
  :connect;[host [port] [pass] [user] [real]]
  :connect;
  :disconnect;
  :quit;
.TE

M scripts/compile_commands.sh => scripts/compile_commands.sh +2 -2
@@ 2,9 2,9 @@

set -e

rm -f compile_commands.json

export CC=clang
export CC_EXT="-Wno-empty-translation-unit"

rm -f compile_commands.json

bear make clean rirc.debug

M scripts/coverage.sh => scripts/coverage.sh +32 -23
@@ 5,34 5,46 @@ set -e
CDIR="coverage"

export CC=gcc
export CC_EXT="-fprofile-arcs -ftest-coverage"
export CC_EXT="-fprofile-arcs -ftest-coverage -fprofile-abs-path"
export LD_EXT="-fprofile-arcs"

make -e clean test
export MAKEFLAGS="-e -j $(nproc)"

rm -rf $CDIR
mkdir -p $CDIR
rm -rf $CDIR && mkdir -p $CDIR

find . -name "*.gcno" -print0 | xargs -0 -I % mv % $CDIR
find . -name "*.gcda" -print0 | xargs -0 -I % mv % $CDIR
make clean
make check

FILTER=$(cat <<'EOF'
GCNO=$(find bld -name '*.t.gcno')

FILTER=$(cat << 'EOF'
{
	use Cwd;
	@results;
	@result_ds;
	@result_fs;
	if (eof()) {
		$cov = ($lc / $lt) * 100.0;
		printf("~\n");
		printf("~ total %21d/%d %7.2f%%\n", $lc, $lt, $cov);
		print "- Coverage:";
		print "- ", "=" x 40;
		print "- $_", for sort(@result_fs);
		print "- $_", for sort(@result_ds);
		print "- ", "=" x 40;
		printf("- Total %20d/%d  %6.2f%%\n", $lc, $lt, (($lc / $lt) * 100.0));
	} elsif ($p) {
		chomp $file;
		chomp $_;
		$file =~ s/'//g;
		my @s1 = split / /, $file;
		my @s2 = split /:/, $_;
		my @s3 = split / /, $s2[1];
		chop($s3[0]);
		printf("%-30s%4s %7s%%\n", $s1[1], $s3[2], $s3[0]);
		$lt = $lt + $s3[2];
		$lc = $lc + $s3[2] * ($s3[0] / 100.0);
		$file = substr($file, (length(getcwd()) + 6));
		@s1 = split /:/, $_;
		@s2 = split / /, $s1[1];
		$lt = $lt + $s2[2];
		$lc = $lc + $s2[2] * ($s2[0] / 100.0);
		$result = sprintf("%-25s  %4s  %7s", $file, $s2[2], $s2[0]);
		if ($file =~ m|src/.*/.*|) {
			push @result_fs, $result;
		} else {
			push @result_ds, $result;
		}
		$p = 0;
	}
	$p++ if /^File.*src.*c'/;


@@ 41,13 53,10 @@ FILTER=$(cat <<'EOF'
EOF
)

echo "~ Coverage:"

gcov -pr $CDIR/*.gcno | perl -ne "$FILTER" | sort
gcov --preserve-paths $GCNO | perl -lne "$FILTER"

find . -name "*gperf*.gcov" -print0 | xargs -0 -I % rm %
find . -name "*test#*.gcov" -print0 | xargs -0 -I % rm %
mv *.gcov $CDIR

if [ -x "$(command -v gcovr)" ]; then
	gcovr -r . --html --html-details --filter "src.*c$" -o $CDIR/index.html
	gcovr -r . --html --html-details --filter ".*src.*c$" -o $CDIR/index.html
fi

M scripts/pre-commit.sh => scripts/pre-commit.sh +1 -1
@@ 5,7 5,7 @@

echo "Running pre-commit hook..."

RESULTS=$(make test)
RESULTS=$(make check)

if [[ "$RESULTS" == *"failure"* ]];
then

R scripts/coverity_get.sh => scripts/sa_coverity_get.sh +9 -7
@@ 12,16 12,18 @@ if [[ -z "${COVERITY_TOKEN}" ]]; then
	fail "missing env COVERITY_TOKEN"
fi

COVERITY_MD5="$1/coverity_tool.md5"
COVERITY_TGZ="$1/coverity_tool.tgz"
DIR="$1"

mkdir "$1"
COVERITY_MD5="$DIR/coverity_tool.md5"
COVERITY_TGZ="$DIR/coverity_tool.tgz"

echo "curl https://scan.coverity.com/download/linux64 ..."
mkdir -p "$DIR"

echo "*" > "$DIR/.gitignore"

curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_TGZ" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc"
curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_MD5" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc&md5=1"
curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_TGZ" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc"

printf "%s\t$COVERITY_TGZ" "$(cat "$COVERITY_MD5")" | md5sum -c -
printf "%s\t$COVERITY_TGZ" "$(cat "$COVERITY_MD5")" | md5sum --quiet -c -

tar xzf "$COVERITY_TGZ" -C "$1" --strip-components 1
tar xzf "$COVERITY_TGZ" -C "$DIR" --strip-components 1

R scripts/coverity_run.sh => scripts/sa_coverity_run.sh +15 -5
@@ 16,18 16,28 @@ if [[ -z "${COVERITY_TOKEN}" ]]; then
	fail "missing env COVERITY_TOKEN"
fi

DIR="$1"

COVERITY_OUT="cov-int"
COVERITY_TAR="cov-int.tgz"

VERSION=$(git rev-parse --short HEAD)

PATH=$(pwd)/$1/bin:$PATH cov-build --dir "$COVERITY_OUT" make clean rirc rirc.debug test
export PATH="$PWD/$DIR/bin:$PATH"

make clean
make libs

cov-build --dir "$COVERITY_OUT" make all check

tar czf "$COVERITY_TAR" "$COVERITY_OUT"

curl \
	--form file=@"$COVERITY_TAR" \
curl https://scan.coverity.com/builds?project=rcr%2Frirc \
	--form description="$VERSION" \
	--form email="$COVERITY_EMAIL" \
	--form file=@"$COVERITY_TAR" \
	--form token="$COVERITY_TOKEN" \
	--form version="$VERSION" \
	https://scan.coverity.com/builds?project=rcr%2Frirc
	--form version="$VERSION"

mv $COVERITY_OUT "$DIR"
mv $COVERITY_TAR "$DIR"

A scripts/sa_sonarcloud_get.sh => scripts/sa_sonarcloud_get.sh +32 -0
@@ 0,0 1,32 @@
#!/bin/bash

set -e

fail() { >&2 printf "%s\n" "$*"; exit 1; }

if [[ -z $1 ]]; then
	fail "Usage: '$0 dir'"
fi

SONAR_VER="4.6.0.2311"

BUILD_ZIP="$1/build-wrapper.zip"
SONAR_ZIP="$1/sonar-scanner.zip"
SONAR_MD5="$1/sonar-scanner.md5"

BUILD_ZIP_URL="https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip"
SONAR_ZIP_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip"
SONAR_MD5_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip.md5"

mkdir -p "$1"

echo "*" > "$1/.gitignore"

curl -fs --show-error "$BUILD_ZIP_URL" -o "$BUILD_ZIP"
curl -fs --show-error "$SONAR_ZIP_URL" -o "$SONAR_ZIP"
curl -fs --show-error "$SONAR_MD5_URL" -o "$SONAR_MD5"

printf "%s\t$SONAR_ZIP" "$(cat "$SONAR_MD5")" | md5sum --quiet -c -

unzip -qq "$BUILD_ZIP" -d "$1"
unzip -qq "$SONAR_ZIP" -d "$1"

A scripts/sa_sonarcloud_run.sh => scripts/sa_sonarcloud_run.sh +57 -0
@@ 0,0 1,57 @@
#!/bin/bash

set -e

fail() { >&2 printf "%s\n" "$*"; exit 1; }

if [[ -z $1 ]]; then
	fail "Usage: '$0 dir'"
fi

DIR="$1"

SONAR_VER="4.6.0.2311"

SONAR_CONFIG="$DIR/sonar-project.properties"

SONAR_SCANNER_BIN="$DIR/sonar-scanner-$SONAR_VER-linux/bin/sonar-scanner"
BUILD_WRAPPER_BIN="$DIR/build-wrapper-linux-x86/build-wrapper-linux-x86-64"
BUILD_WRAPPER_OUT="$DIR/bw-out"

if [[ ! -f "$BUILD_WRAPPER_BIN" ]]; then
	fail "missing build-wrapper binary"
fi

if [[ ! -f "$SONAR_SCANNER_BIN" ]]; then
	fail "missing sonar-scanner binary"
fi

cat << EOF >> "$SONAR_CONFIG"
# Server
sonar.host.url = https://sonarcloud.io

# Project
sonar.organization   = rirc
sonar.projectKey     = rirc
sonar.projectName    = rirc
sonar.projectVersion = $(git rev-parse --short HEAD)
sonar.branch.name    = $(git name-rev --name-only HEAD)
sonar.links.homepage = https://rcr.io/rirc/
sonar.links.scm      = https://git.sr.ht/~rcr/rirc/
sonar.links.ci       = https://builds.sr.ht/~rcr/rirc/

# C, Sources
sonar.cfamily.build-wrapper-output = $BUILD_WRAPPER_OUT
sonar.cfamily.cache.enabled        = false
sonar.cfamily.threads              = $(nproc)
sonar.sources                      = src

# Output
sonar.working.directory = $DIR/scannerwork
EOF

make clean
make libs

eval "$BUILD_WRAPPER_BIN --out-dir $BUILD_WRAPPER_OUT make all check"
eval "$SONAR_SCANNER_BIN --define project.settings=$SONAR_CONFIG"

M scripts/sanitizers_build.sh => scripts/sanitizers_build.sh +1 -1
@@ 11,7 11,7 @@ make -e clean rirc.debug

mv rirc.debug rirc.debug.address

export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
export CC_EXT="-fsanitize=thread,undefined -fno-omit-frame-pointer"
export LD_EXT="-fsanitize=thread,undefined"

make -e clean rirc.debug

M scripts/sanitizers_test.sh => scripts/sanitizers_test.sh +2 -3
@@ 3,11 3,10 @@
set -e

export CC=clang

export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
export LD_EXT="-fsanitize=address,undefined"
export LD_EXT="-fsanitize=address,undefined -fuse-ld=lld"

# for core dumps:
# export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0"

make -e test
make -e clean check

M src/components/buffer.c => src/components/buffer.c +7 -4
@@ 1,7 1,10 @@
#include <string.h>

#include "src/components/buffer.h"

#include "config.h"
#include "src/utils/utils.h"

#include <string.h>

#define BUFFER_MASK(X) ((X) & (BUFFER_LINES_MAX - 1))

#if BUFFER_MASK(BUFFER_LINES_MAX)


@@ 114,7 117,7 @@ buffer_line_rows(struct buffer_line *line, unsigned int w)
		line->cached.w = w;

		for (p = line->text, line->cached.rows = 0; *p; line->cached.rows++)
			word_wrap(w, &p, line->text + line->text_len);
			irc_strwrap(w, &p, line->text + line->text_len);
	}

	return line->cached.rows;


@@ 123,7 126,7 @@ buffer_line_rows(struct buffer_line *line, unsigned int w)
void
buffer_newline(
		struct buffer *b,
		enum buffer_line_t type,
		enum buffer_line_type type,
		const char *from_str,
		const char *text_str,
		size_t from_len,

M src/components/buffer.h => src/components/buffer.h +5 -8
@@ 1,11 1,8 @@
#ifndef BUFFER_H
#define BUFFER_H
#ifndef RIRC_COMPONENTS_BUFFER_H
#define RIRC_COMPONENTS_BUFFER_H

#include <time.h>

#include "src/utils/utils.h"
#include "config.h"

#define TEXT_LENGTH_MAX 510 /* FIXME: remove max lengths in favour of growable buffer */
#define FROM_LENGTH_MAX 100



@@ 14,7 11,7 @@
#endif

/* Buffer line types, in order of precedence */
enum buffer_line_t
enum buffer_line_type
{
	BUFFER_LINE_OTHER,        /* Default/all other lines */
	BUFFER_LINE_SERVER_INFO,  /* Server info message */


@@ 30,7 27,7 @@ enum buffer_line_t

struct buffer_line
{
	enum buffer_line_t type;
	enum buffer_line_type type;
	char prefix; /* TODO as part of `from` */
	char from[FROM_LENGTH_MAX + 1]; /* TODO: from/text as struct string */
	char text[TEXT_LENGTH_MAX + 1];


@@ 69,7 66,7 @@ struct buffer_line* buffer_line(struct buffer*, unsigned int);

void buffer_newline(
	struct buffer*,
	enum buffer_line_t,
	enum buffer_line_type,
	const char*,
	const char*,
	size_t,

M src/components/channel.c => src/components/channel.c +10 -5
@@ 1,12 1,13 @@
#include "src/components/channel.h"

#include "src/utils/utils.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "src/components/channel.h"
#include "src/utils/utils.h"

struct channel*
channel(const char *name, enum channel_t type)
channel(const char *name, enum channel_type type)
{
	struct channel *c;



@@ 52,6 53,8 @@ channel_list_free(struct channel_list *cl)
void
channel_list_add(struct channel_list *cl, struct channel *c)
{
	cl->count++;

	if (cl->head == NULL) {
		cl->head = c->next = c;
		cl->tail = c->prev = c;


@@ 67,6 70,8 @@ channel_list_add(struct channel_list *cl, struct channel *c)
void
channel_list_del(struct channel_list *cl, struct channel *c)
{
	cl->count--;

	if (cl->head == c && cl->tail == c) {
		cl->head = NULL;
		cl->tail = NULL;


@@ 86,7 91,7 @@ channel_list_del(struct channel_list *cl, struct channel *c)
}

struct channel*
channel_list_get(struct channel_list *cl, const char *name, enum casemapping_t cm)
channel_list_get(struct channel_list *cl, const char *name, enum casemapping cm)
{
	struct channel *tmp;


M src/components/channel.h => src/components/channel.h +10 -9
@@ 1,5 1,5 @@
#ifndef CHANNEL_H
#define CHANNEL_H
#ifndef RIRC_COMPONENTS_CHANNEL_H
#define RIRC_COMPONENTS_CHANNEL_H

#include "src/components/buffer.h"
#include "src/components/input.h"


@@ 7,7 7,7 @@
#include "src/components/user.h"

/* Channel activity types, in order of precedence */
enum activity_t
enum activity
{
	ACTIVITY_DEFAULT, /* Default activity */
	ACTIVITY_JPQ,     /* Join/Part/Quit activity */


@@ 16,10 16,10 @@ enum activity_t
	ACTIVITY_T_SIZE
};

enum channel_t
enum channel_type
{
	CHANNEL_T_INVALID,
	CHANNEL_T_OTHER,   /* Default/all other buffers */
	CHANNEL_T_RIRC,    /* Default buffer */
	CHANNEL_T_CHANNEL, /* Channel message buffer */
	CHANNEL_T_SERVER,  /* Server message buffer */
	CHANNEL_T_PRIVATE, /* Private message buffer */


@@ 29,8 29,8 @@ enum channel_t
struct channel
{
	const char *name;
	enum activity_t activity;
	enum channel_t type;
	enum activity activity;
	enum channel_type type;
	size_t name_len;
	struct buffer buffer;
	struct channel *next;


@@ 49,10 49,11 @@ struct channel_list
{
	struct channel *head;
	struct channel *tail;
	unsigned count;
};

struct channel* channel(const char*, enum channel_t);
struct channel* channel_list_get(struct channel_list*, const char*, enum casemapping_t);
struct channel* channel(const char*, enum channel_type);
struct channel* channel_list_get(struct channel_list*, const char*, enum casemapping);
void channel_free(struct channel*);
void channel_list_add(struct channel_list*, struct channel*);
void channel_list_del(struct channel_list*, struct channel*);

M src/components/input.c => src/components/input.c +4 -4
@@ 1,10 1,10 @@
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include "src/components/input.h"

#include "src/utils/utils.h"

#include <errno.h>
#include <string.h>

#define INPUT_MASK(X) ((X) & (INPUT_HIST_MAX - 1))

#if INPUT_MASK(INPUT_HIST_MAX)

M src/components/input.h => src/components/input.h +3 -2
@@ 1,5 1,5 @@
#ifndef INPUT_H
#define INPUT_H
#ifndef RIRC_COMPONENTS_INPUT_H
#define RIRC_COMPONENTS_INPUT_H

/* Buffer input
 *


@@ 13,6 13,7 @@
 * copied into the working area when scrolling
 */

#include <stddef.h>
#include <stdint.h>

/* 410 max characters for input should be sufficient given

M src/components/ircv3.c => src/components/ircv3.c +3 -3
@@ 18,9 18,9 @@ void
ircv3_caps(struct ircv3_caps *caps)
{
	#define X(CAP, VAR, ATTRS) \
	caps->VAR.req = 0;                                    \
	caps->VAR.set = 0;                                    \
	caps->VAR.supported = 0;                              \
	caps->VAR.req = 0; \
	caps->VAR.set = 0; \
	caps->VAR.supported = 0; \
	caps->VAR.supports_del = !(ATTRS & IRCV3_CAP_NO_DEL); \
	caps->VAR.supports_req = !(ATTRS & IRCV3_CAP_NO_REQ); \
	caps->VAR.req_auto = (ATTRS & IRCV3_CAP_AUTO);

M src/components/ircv3.h => src/components/ircv3.h +10 -4
@@ 1,13 1,19 @@
#ifndef IRCV3_CAP_H
#define IRCV3_CAP_H
#ifndef RIRC_COMPONENTS_IRCV3_CAP_H
#define RIRC_COMPONENTS_IRCV3_CAP_H

#define IRCV3_CAP_AUTO   (1 << 0)
#define IRCV3_CAP_NO_DEL (1 << 1)
#define IRCV3_CAP_NO_REQ (1 << 2)

#define IRCV3_CAP_VERSION "302"

#define IRCV3_CAPS_DEF \
	X("multi-prefix", multi_prefix, IRCV3_CAP_AUTO)
	X("account-notify", account_notify, IRCV3_CAP_AUTO) \
	X("away-notify",    away_notify,    IRCV3_CAP_AUTO) \
	X("chghost",        chghost,        IRCV3_CAP_AUTO) \
	X("extended-join",  extended_join,  IRCV3_CAP_AUTO) \
	X("invite-notify",  invite_notify,  IRCV3_CAP_AUTO) \
	X("multi-prefix",   multi_prefix,   IRCV3_CAP_AUTO)

/* Extended by testcases */
#ifndef IRCV3_CAPS_TEST


@@ 21,7 27,7 @@
struct ircv3_cap
{
	unsigned req          : 1; /* cap REQ sent */
	unsigned req_auto     : 1; /* cap REQ sent during registration */
	unsigned req_auto     : 1; /* cap REQ sent automatically */
	unsigned set          : 1; /* cap is unset/set */
	unsigned supported    : 1; /* cap is supported by server */
	unsigned supports_del : 1; /* cap supports CAP DEL */

M src/components/mode.c => src/components/mode.c +47 -53
@@ 1,38 1,37 @@
/* TODO: safe channels ('!' prefix) (see RFC2811) */
#include "src/components/mode.h"

#include "src/utils/utils.h"

#include <ctype.h>
#include <string.h>

#include "src/components/mode.h"
#include "src/utils/utils.h"

#define MODE_ISLOWER(X) ((X) >= 'a' && (X) <= 'z')
#define MODE_ISUPPER(X) ((X) >= 'A' && (X) <= 'Z')

/* Set bit Y of X to the value of Z: [0, 1] */
#define MODE_SET(X, Y, Z) ((X) ^= (-(Z) ^ (X)) & (Y))

enum mode_chanmode_prefix_t
enum mode_chanmode_prefix
{
	MODE_CHANMODE_PREFIX_SECRET  = '@', /* chanmode 's' */
	MODE_CHANMODE_PREFIX_PRIVATE = '*', /* chanmode 'p' */
	MODE_CHANMODE_PREFIX_OTHER   = '=',
	MODE_CHANMODE_PREFIX_T_SIZE
};

static inline int mode_isset(const struct mode*, int);
static inline uint32_t flag_bit(int);

static enum mode_err_t mode_cfg_chanmodes(struct mode_cfg*, const char*);
static enum mode_err_t mode_cfg_usermodes(struct mode_cfg*, const char*);
static enum mode_err_t mode_cfg_subtypes(struct mode_cfg*, const char*);
static enum mode_err_t mode_cfg_prefix(struct mode_cfg*, const char*);
static enum mode_err_t mode_cfg_modes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_chanmodes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_usermodes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_subtypes(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_prefix(struct mode_cfg*, const char*);
static enum mode_err mode_cfg_modes(struct mode_cfg*, const char*);

/* TODO: check validity of set_t on all mode settings */
/* TODO: static inline void mode_bit_set(struct mode*, uint32_t); */
/* TODO: static inline void mode_bit_isset(struct mode*, uint32_t); */
/* TODO: aggregate errors with logging callback */
/* TODO: safe channels ('!' prefix) (see RFC2811) */

static inline int
mode_isset(const struct mode *m, int flag)


@@ 74,8 73,8 @@ flag_bit(int c)
	return 0;
}

enum mode_err_t
mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_t cfg_type)
enum mode_err
mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
{
	/* Initialize a mode_cfg to the RFC2812, RFC2811 defaults
	 *


@@ 163,8 162,8 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_t cfg_type)
	return MODE_ERR_NONE;
}

enum mode_err_t
mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set_t set_t)
enum mode_err
mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
{
	/* Set/unset chanmode flags
	 *


@@ 203,13 202,13 @@ mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
	 * (mentioned in the "IRC Server Protocol" document [IRC-SERVER]).
	 */

	if (!(set_t == MODE_SET_ON || set_t == MODE_SET_OFF))
	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;

	if (!mode_isset(&(cfg->chanmodes), flag))
		return MODE_ERR_INVALID_FLAG;

	if (set_t == MODE_SET_ON && mode_isset(m, flag))
	if (set == MODE_SET_ON && mode_isset(m, flag))
		return MODE_ERR_DUPLICATE;

	/* Mode subtypes A don't set a flag */


@@ 221,9 220,9 @@ mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
		uint32_t bit = flag_bit(flag);

		if (MODE_ISLOWER(flag))
			MODE_SET(m->lower, bit, set_t);
			MODE_SET(m->lower, bit, set);
		else
			MODE_SET(m->upper, bit, set_t);
			MODE_SET(m->upper, bit, set);

	} else if (flag == 'p') {



@@ 231,13 230,13 @@ mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
		if (mode_isset(m, 's'))
			return MODE_ERR_NONE;

		if (set_t == MODE_SET_OFF) {
		if (set == MODE_SET_OFF) {
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);

			m->prefix = MODE_CHANMODE_PREFIX_OTHER;
		}

		if (set_t == MODE_SET_ON) {
		if (set == MODE_SET_ON) {
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_ON);

			m->prefix = MODE_CHANMODE_PREFIX_PRIVATE;


@@ 245,14 244,14 @@ mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod

	} else if (flag == 's') {

		if (set_t == MODE_SET_OFF) {
		if (set == MODE_SET_OFF) {
			MODE_SET(m->lower, flag_bit('s'), MODE_SET_OFF);
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);

			m->prefix = MODE_CHANMODE_PREFIX_OTHER;
		}

		if (set_t == MODE_SET_ON) {
		if (set == MODE_SET_ON) {
			MODE_SET(m->lower, flag_bit('s'), MODE_SET_ON);
			MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);



@@ 263,14 262,14 @@ mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
	return MODE_ERR_NONE;
}

enum mode_err_t
mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set_t set_t)
enum mode_err
mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
{
	/* Set/unset prfxmode flags and mode prefix */

	uint32_t bit;

	if (!(set_t == MODE_SET_ON || set_t == MODE_SET_OFF))
	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;

	if (!strchr(cfg->PREFIX.F, flag))


@@ 279,9 278,9 @@ mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
	bit = flag_bit(flag);

	if (MODE_ISLOWER(flag))
		MODE_SET(m->lower, bit, set_t);
		MODE_SET(m->lower, bit, set);
	else
		MODE_SET(m->upper, bit, set_t);
		MODE_SET(m->upper, bit, set);

	const char *f = cfg->PREFIX.F,
	           *t = cfg->PREFIX.T;


@@ 300,14 299,14 @@ mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
	return MODE_ERR_NONE;
}

enum mode_err_t
mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set_t set_t)
enum mode_err
mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
{
	/* Set/unset usermode flags */

	uint32_t bit;

	if (!(set_t == MODE_SET_ON || set_t == MODE_SET_OFF))
	if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
		return MODE_ERR_INVALID_SET;

	if (!mode_isset(&(cfg->usermodes), flag))


@@ 316,14 315,14 @@ mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
	bit = flag_bit(flag);

	if (MODE_ISLOWER(flag))
		MODE_SET(m->lower, bit, set_t);
		MODE_SET(m->lower, bit, set);
	else
		MODE_SET(m->upper, bit, set_t);
		MODE_SET(m->upper, bit, set);

	return MODE_ERR_NONE;
}

enum mode_err_t
enum mode_err
mode_chanmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
{
	/* Set chanmode flag and prefix give the prefix character, e.g.:


@@ 365,7 364,7 @@ mode_chanmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
	return MODE_ERR_NONE;
}

enum mode_err_t
enum mode_err
mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
{
	/* Set prfxmode flag and prefix given the prefix character, e.g.: 


@@ 380,15 379,14 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
	const char *f = cfg->PREFIX.F,
	           *t = cfg->PREFIX.T;

	while (*t != flag) {

		if (*t == 0)
			return MODE_ERR_INVALID_PREFIX;

	while (*t && *t != flag) {
		f++;
		t++;
	}

	if (*t == 0)
		return MODE_ERR_INVALID_PREFIX;

	bit = flag_bit(*f);

	if (MODE_ISLOWER(*f))


@@ 399,11 397,7 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
	f = cfg->PREFIX.F,
	t = cfg->PREFIX.T;

	while (*f) {

		if (mode_isset(m, *f))
			break;

	while (!mode_isset(m, *f)) {
		f++;
		t++;
	}


@@ 456,7 450,7 @@ mode_reset(struct mode *m, struct mode_str *s)
	if (!m || !s)
		fatal("mode or mode_str is null");

	enum mode_str_t type = s->type;
	enum mode_str_type type = s->type;

	memset(m, 0, sizeof(*m));
	memset(s, 0, sizeof(*s));


@@ 464,7 458,7 @@ mode_reset(struct mode *m, struct mode_str *s)
	s->type = type;
}

static enum mode_err_t
static enum mode_err
mode_cfg_chanmodes(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure chanmodes string */


@@ 494,7 488,7 @@ mode_cfg_chanmodes(struct mode_cfg *cfg, const char *str)
	return MODE_ERR_NONE;
}

static enum mode_err_t
static enum mode_err
mode_cfg_usermodes(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure usermodes string */


@@ 524,7 518,7 @@ mode_cfg_usermodes(struct mode_cfg *cfg, const char *str)
	return MODE_ERR_NONE;
}

static enum mode_err_t
static enum mode_err
mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure CHANMODE subtypes, e.g.:


@@ 589,7 583,7 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
	return MODE_ERR_NONE;
}

static enum mode_err_t
static enum mode_err
mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure PREFIX e.g.:


@@ 665,7 659,7 @@ error:
	return MODE_ERR_INVALID_CONFIG;
}

static enum mode_err_t
static enum mode_err
mode_cfg_modes(struct mode_cfg *cfg, const char *str)
{
	/* Parse and configure MODES, valid values are numeric strings [1-99] */


@@ 687,8 681,8 @@ mode_cfg_modes(struct mode_cfg *cfg, const char *str)
	return MODE_ERR_NONE;
}

enum chanmode_flag_t
chanmode_type(const struct mode_cfg *cfg, enum mode_set_t set, int flag)
enum chanmode_flag_type
chanmode_type(const struct mode_cfg *cfg, enum mode_set set, int flag)
{
	/* Return the chanmode flag type specified by config */


M src/components/mode.h => src/components/mode.h +15 -18
@@ 1,5 1,5 @@
#ifndef MODE_H
#define MODE_H
#ifndef RIRC_COMPONENTS_MODE_H
#define RIRC_COMPONENTS_MODE_H

/* usermodes, chanmodes and prfxmode configuration
 *


@@ 49,7 49,7 @@
    .upper  = 0, \
}

enum mode_err_t
enum mode_err
{
	MODE_ERR_DUPLICATE      = -5,
	MODE_ERR_INVALID_CONFIG = -4,


@@ 59,7 59,7 @@ enum mode_err_t
	MODE_ERR_NONE
};

enum mode_cfg_t
enum mode_cfg_type
{
	MODE_CFG_DEFAULTS,  /* Set RFC2811 mode defaults */
	MODE_CFG_CHANMODES, /* Set numeric 004 chanmdoes string */


@@ 67,25 67,22 @@ enum mode_cfg_t
	MODE_CFG_PREFIX,    /* Set numeric 005 PREFIX */
	MODE_CFG_SUBTYPES,  /* Set numeric 005 CHANMODES subtypes */
	MODE_CFG_MODES,     /* Set numeric 005 MODES */
	MODE_CFG_T_SIZE
};

enum mode_set_t
enum mode_set
{
	MODE_SET_OFF = 0,
	MODE_SET_ON  = 1,
	MODE_SET_ON = 1,
	MODE_SET_INVALID,
	MODE_SET_T_SIZE
};

enum chanmode_flag_t
enum chanmode_flag_type
{
	MODE_FLAG_INVALID_FLAG,
	MODE_FLAG_INVALID_SET,
	MODE_FLAG_CHANMODE,       /* Chanmode flag without parameter */
	MODE_FLAG_CHANMODE_PARAM, /* Chanmode flag with parameter */
	MODE_FLAG_PREFIX,         /* Chanmode flag that sets prfxmode */
	MODE_FLAG_T_SIZE
};

struct mode


@@ 117,7 114,7 @@ struct mode_cfg
struct mode_str
{
	char str[MODE_STR_LEN + 1];
	enum mode_str_t
	enum mode_str_type
	{
		MODE_STR_UNSET = 0,
		MODE_STR_CHANMODE,


@@ 128,13 125,13 @@ struct mode_str
};

const char* mode_str(const struct mode*, struct mode_str*);
enum chanmode_flag_t chanmode_type(const struct mode_cfg*, enum mode_set_t, int);
enum mode_err_t mode_cfg(struct mode_cfg*, const char*, enum mode_cfg_t);
enum mode_err_t mode_chanmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err_t mode_chanmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set_t);
enum mode_err_t mode_prfxmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err_t mode_prfxmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set_t);
enum mode_err_t mode_usermode_set(struct mode*, const struct mode_cfg*, int, enum mode_set_t);
enum chanmode_flag_type chanmode_type(const struct mode_cfg*, enum mode_set, int);
enum mode_err mode_cfg(struct mode_cfg*, const char*, enum mode_cfg_type);
enum mode_err mode_chanmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err mode_chanmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
enum mode_err mode_prfxmode_prefix(struct mode*, const struct mode_cfg*, int);
enum mode_err mode_prfxmode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
enum mode_err mode_usermode_set(struct mode*, const struct mode_cfg*, int, enum mode_set);
void mode_reset(struct mode*, struct mode_str*);

#endif

M src/components/server.c => src/components/server.c +18 -24
@@ 1,12 1,13 @@
#include "src/components/server.h"

#include "src/state.h"
#include "src/utils/utils.h"

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "src/components/server.h"
#include "src/state.h"
#include "src/utils/utils.h"

#define HANDLED_005 \
	X(CASEMAPPING)  \
	X(CHANMODES)    \


@@ 39,13 40,12 @@ server(const char *host, const char *port, const char *pass, const char *user, c
	s->pass = pass ? strdup(pass) : NULL;
	s->username = strdup(user);
	s->realname = strdup(real);
	s->channel = channel(host, CHANNEL_T_SERVER);
	s->casemapping = CASEMAPPING_RFC1459;
	s->mode_str.type = MODE_STR_USERMODE;
	ircv3_caps(&(s->ircv3_caps));
	mode_cfg(&(s->mode_cfg), NULL, MODE_CFG_DEFAULTS);
	/* FIXME: remove server pointer from channel, remove
	 * server's channel from clist */

	s->channel = channel(host, CHANNEL_T_SERVER);
	s->channel->server = s;
	channel_list_add(&(s->clist), s->channel);



@@ 75,9 75,7 @@ server_list_get(struct server_list *sl, const char *host, const char *port)
struct server*
server_list_add(struct server_list *sl, struct server *s)
{
	struct server *tmp;

	if ((tmp = server_list_get(sl, s->host, s->port)) != NULL)
	if (server_list_get(sl, s->host, s->port) != NULL)
		return s;

	if (sl->head == NULL) {


@@ 142,12 140,8 @@ server_reset(struct server *s)
void
server_free(struct server *s)
{
	// FIXME: add this back when removing it from
	// server's channel_list
	// channel_free(s->channel);

	channel_list_free(&(s->clist));
	channel_list_free(&(s->ulist));

	user_list_free(&(s->ignore));

	free((void *)s->host);


@@ 166,21 160,21 @@ server_set_004(struct server *s, char *str)
{
	/* <server_name> <version> <user_modes> <chan_modes> */

	const char *server_name; /* Not used */
	const char *version;     /* Not used */
	const char *user_modes;  /* Configure server usermodes */
	const char *chan_modes;  /* Configure server chanmodes */
	const char *user_modes;
	const char *chan_modes;

	if (!(server_name = strsep(&str)))
	/* Not used */
	if (!irc_strsep(&str))
		server_error(s, "invalid numeric 004: server_name is null");

	if (!(version = strsep(&str)))
	/* Not used */
	if (!irc_strsep(&str))
		server_error(s, "invalid numeric 004: version is null");

	if (!(user_modes = strsep(&str)))
	if (!(user_modes = irc_strsep(&str)))
		server_error(s, "invalid numeric 004: user_modes is null");

	if (!(chan_modes = strsep(&str)))
	if (!(chan_modes = irc_strsep(&str)))
		server_error(s, "invalid numeric 004: chan_modes is null");

	if (user_modes) {


@@ 327,7 321,7 @@ parse_005(struct opt *opt, char **str)
	opt->arg = NULL;
	opt->val = NULL;

	if (!strtrim(&p))
	if (!irc_strtrim(&p))
		return 0;

	if (!isalnum(*p))

M src/components/server.h => src/components/server.h +5 -5
@@ 1,5 1,5 @@
#ifndef SERVER_H
#define SERVER_H
#ifndef RIRC_COMPONENTS_SERVER_H
#define RIRC_COMPONENTS_SERVER_H

#include "src/components/buffer.h"
#include "src/components/channel.h"


@@ 17,7 17,7 @@ struct server
	const char *username;
	const char *realname;
	const char *nick;
	enum casemapping_t casemapping;
	enum casemapping casemapping;
	struct {
		size_t next;
		size_t size;


@@ 26,7 26,6 @@ struct server
	} nicks;
	struct channel *channel;
	struct channel_list clist;
	struct channel_list ulist; // TODO: seperate privmsg
	struct ircv3_caps ircv3_caps;
	struct mode usermodes;
	struct mode_str mode_str;


@@ 35,7 34,8 @@ struct server
	struct server *prev;
	struct user_list ignore;
	unsigned ping;
	unsigned quitting : 1;
	unsigned connected  : 1;
	unsigned quitting   : 1;
	unsigned registered : 1;
	void *connection;
	// TODO: move this to utils

M src/components/user.c => src/components/user.c +19 -19
@@ 1,10 1,9 @@
#include "src/components/user.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "src/components/user.h"
#include "src/utils/utils.h"

static struct user* user(const char*, struct mode);
static inline int user_cmp(struct user*, struct user*, void *arg);
static inline int user_ncmp(struct user*, struct user*, void *arg, size_t);


@@ 15,13 14,13 @@ AVL_GENERATE(user_list, user, ul, user_cmp, user_ncmp)
static inline int
user_cmp(struct user *u1, struct user *u2, void *arg)
{
	return irc_strcmp(*(enum casemapping_t*)arg, u1->nick, u2->nick);
	return irc_strcmp(*(enum casemapping*)arg, u1->nick, u2->nick);
}

static inline int
user_ncmp(struct user *u1, struct user *u2, void *arg, size_t n)
{
	return irc_strncmp(*(enum casemapping_t*)arg, u1->nick, u2->nick, n);
	return irc_strncmp(*(enum casemapping*)arg, u1->nick, u2->nick, n);
}

static inline void


@@ 39,15 38,15 @@ user(const char *nick, struct mode prfxmodes)
	if ((u = calloc(1, sizeof(*u) + len + 1)) == NULL)
		fatal("calloc: %s", strerror(errno));

	u->nick_len = len;
	u->nick = memcpy(u->_, nick, len + 1);
	u->nick_len = len;
	u->prfxmodes = prfxmodes;

	return u;
}

enum user_err
user_list_add(struct user_list *ul, enum casemapping_t cm, const char *nick, struct mode prfxmodes)
user_list_add(struct user_list *ul, enum casemapping cm, const char *nick, struct mode prfxmodes)
{
	/* Create user and add to userlist */



@@ 61,7 60,7 @@ user_list_add(struct user_list *ul, enum casemapping_t cm, const char *nick, str
}

enum user_err
user_list_del(struct user_list *ul, enum casemapping_t cm, const char *nick)
user_list_del(struct user_list *ul, enum casemapping cm, const char *nick)
{
	/* Delete user and remove from userlist */



@@ 79,22 78,26 @@ user_list_del(struct user_list *ul, enum casemapping_t cm, const char *nick)
}

enum user_err
user_list_rpl(struct user_list *ul, enum casemapping_t cm, const char *nick_old, const char *nick_new)
user_list_rpl(struct user_list *ul, enum casemapping cm, const char *nick_old, const char *nick_new)
{
	/* Replace a user in a list by name, maintaining modes */
	/* Replace a user by name, maintaining modes */

	struct user *old, *new;

	if ((old = user_list_get(ul, cm, nick_old, 0)) == NULL)
	old = user_list_get(ul, cm, nick_old, 0);
	new = user_list_get(ul, cm, nick_new, 0);

	if (old == NULL)
		return USER_ERR_NOT_FOUND;

	if ((new = user_list_get(ul, cm, nick_new, 0)) != NULL)
	/* allow nick to change case  */
	if (new != NULL && irc_strcmp(cm, old->nick, new->nick))
		return USER_ERR_DUPLICATE;

	new = user(nick_new, old->prfxmodes);

	AVL_ADD(user_list, ul, new, &cm);
	AVL_DEL(user_list, ul, old, &cm);
	AVL_ADD(user_list, ul, new, &cm);

	user_free(old);



@@ 102,14 105,11 @@ user_list_rpl(struct user_list *ul, enum casemapping_t cm, const char *nick_old,
}

struct user*
user_list_get(struct user_list *ul, enum casemapping_t cm, const char *nick, size_t prefix_len)
user_list_get(struct user_list *ul, enum casemapping cm, const char *nick, size_t prefix_len)
{
	struct user u2 = { .nick = nick };
	struct user u = { .nick = nick };

	if (prefix_len == 0)
		return AVL_GET(user_list, ul, &u2, &cm);
	else
		return AVL_NGET(user_list, ul, &u2, &cm, prefix_len);
	return AVL_GET(user_list, ul, &u, &cm, prefix_len);
}

void

M src/components/user.h => src/components/user.h +6 -6
@@ 1,5 1,5 @@
#ifndef NICKLIST_H
#define NICKLIST_H
#ifndef RIRC_COMPONENTS_USER_H
#define RIRC_COMPONENTS_USER_H

#include "src/components/mode.h"
#include "src/utils/tree.h"


@@ 27,10 27,10 @@ struct user_list
	unsigned int count;
};

enum user_err user_list_add(struct user_list*, enum casemapping_t, const char*, struct mode);
enum user_err user_list_del(struct user_list*, enum casemapping_t, const char*);
enum user_err user_list_rpl(struct user_list*, enum casemapping_t, const char*, const char*);
struct user* user_list_get(struct user_list*, enum casemapping_t, const char*, size_t);
enum user_err user_list_add(struct user_list*, enum casemapping, const char*, struct mode);
enum user_err user_list_del(struct user_list*, enum casemapping, const char*);
enum user_err user_list_rpl(struct user_list*, enum casemapping, const char*, const char*);
struct user* user_list_get(struct user_list*, enum casemapping, const char*, size_t);
void user_list_free(struct user_list*);

#endif

M src/draw.c => src/draw.c +73 -92
@@ 1,52 1,41 @@
/* draw.c
 *
 * Draw the elements in state.c to the terminal.
 *
 * Assumes vt-100 compatible escape codes, as such YMMV */

#include <alloca.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "src/draw.h"

#include "config.h"
#include "src/components/channel.h"
#include "src/components/input.h"
#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"

#define ESC "\x1b"

#define RESET_ATTRIBUTES ESC"[0m"
#include <alloca.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FG(X) ESC"[38;5;"#X"m"
#define BG(X) ESC"[48;5;"#X"m"
/* Control sequence initiator */
#define CSI "\x1b["

#define MOVE(X, Y) ESC"["#X";"#Y"H"
#define ATTR_FG(X)   CSI "38;5;"#X"m"
#define ATTR_BG(X)   CSI "48;5;"#X"m"
#define ATTR_RESET   CSI "0m"

#define CLEAR_FULL  ESC"[2J"
#define CLEAR_RIGHT ESC"[0K"
#define CLEAR_LEFT  ESC"[1K"
#define CLEAR_LINE  ESC"[2K"
#define CLEAR_FULL   CSI "2J"
#define CLEAR_LINE   CSI "2K"

/* Save and restore the cursor's location */
#define CURSOR_SAVE    ESC"[s"
#define CURSOR_RESTORE ESC"[u"
#define C_MOVE(X, Y) CSI ""#X";"#Y"H"
#define C_SAVE       CSI "s"
#define C_RESTORE    CSI "u"

/* Minimum rows or columns to safely draw */
#define COLS_MIN 5
#define ROWS_MIN 5

/* Size of a full colour string for purposes of pre-formating text to print */
#define COLOUR_SIZE sizeof(RESET_ATTRIBUTES FG(255) BG(255))
#define COLOUR_SIZE sizeof(ATTR_RESET ATTR_FG(255) ATTR_BG(255))

#ifndef BUFFER_PADDING
#define BUFFER_PADDING 1
#elif BUFFER_PADDING != 0 && BUFFER_PADDING != 1
#error "BUFFER_PADDING options are 0 (no pad), 1 (padded)"
#endif

/* Terminal coordinate row/column boundaries (inclusive)


@@ 127,25 116,15 @@ draw(enum draw_bit bit)
		case DRAW_ALL:
			draw_state.bits.all = -1;
			break;
		case DRAW_CLEAR:
			printf(ATTR_RESET);
			printf(CLEAR_FULL);
			break;
		default:
			fatal("unknown draw bit");
	}
}

void
draw_init(void)
{
	draw(DRAW_ALL);
	draw(DRAW_FLUSH);
}

void
draw_term(void)
{
	printf(RESET_ATTRIBUTES);
	printf(CLEAR_FULL);
}

static void
draw_bits(void)
{


@@ 158,38 137,44 @@ draw_bits(void)
	struct coords coords;
	struct channel *c = current_channel();

	if (io_tty_cols() < COLS_MIN || io_tty_rows() < ROWS_MIN) {
		printf(CLEAR_FULL MOVE(1, 1) "rirc");
	if (state_cols() < COLS_MIN || state_rows() < ROWS_MIN) {
		printf(CLEAR_FULL C_MOVE(1, 1) "rirc");
		fflush(stdout);
		return;
	}

	printf(CURSOR_SAVE);
	printf(C_SAVE);

	if (draw_state.bits.buffer) {
		printf(ATTR_RESET);
		coords.c0 = 1;
		coords.cN = io_tty_cols();
		coords.cN = state_cols();
		coords.r0 = 3;
		coords.rN = io_tty_rows() - 2;
		coords.rN = state_rows() - 2;
		draw_buffer(&c->buffer, coords);
	}

	if (draw_state.bits.input) {
		printf(ATTR_RESET);
		coords.c0 = 1;
		coords.cN = io_tty_cols();
		coords.r0 = io_tty_rows();
		coords.rN = io_tty_rows();
		coords.cN = state_cols();
		coords.r0 = state_rows();
		coords.rN = state_rows();
		draw_input(&c->input, coords);
	}

	if (draw_state.bits.nav)
	if (draw_state.bits.nav) {
		printf(ATTR_RESET);
		draw_nav(c);
	}

	if (draw_state.bits.status)
	if (draw_state.bits.status) {
		printf(ATTR_RESET);
		draw_status(c);
	}

	printf(RESET_ATTRIBUTES);
	printf(CURSOR_RESTORE);
	printf(ATTR_RESET);
	printf(C_RESTORE);

	fflush(stdout);
}


@@ 246,7 231,7 @@ draw_buffer(struct buffer *b, struct coords coords)

	/* Clear the buffer area */
	for (row = coords.r0; row <= coords.rN; row++)
		printf(MOVE(%d, 1) CLEAR_LINE, row);
		printf(C_MOVE(%d, 1) CLEAR_LINE, row);

	struct buffer_line *line = buffer_line(b, buffer_i);



@@ 369,7 354,7 @@ draw_buffer_line(
				"%*s", pad, ""))
			goto print_header;

		if (!draw_fmt(&header_ptr, &buff_n, &text_n, 0, RESET_ATTRIBUTES))
		if (!draw_fmt(&header_ptr, &buff_n, &text_n, 0, ATTR_RESET))
			goto print_header;

		switch (line->type) {


@@ 407,26 392,26 @@ draw_buffer_line(

print_header:
		/* Print the line header */
		printf(MOVE(%d, 1) "%s " RESET_ATTRIBUTES, coords.r0, header);
		printf(C_MOVE(%d, 1) "%s " ATTR_RESET, coords.r0, header);
	}

	while (skip--)
		word_wrap(text_w, &p1, p2);
		irc_strwrap(text_w, &p1, p2);

	do {
		char *sep = " "VERTICAL_SEPARATOR" ";

		if ((coords.cN - coords.c0) >= sizeof(*sep) + text_w) {
			printf(MOVE(%d, %d), coords.r0, (int)(coords.cN - (sizeof(*sep) + text_w + 1)));
			printf(C_MOVE(%d, %d), coords.r0, (int)(coords.cN - (sizeof(*sep) + text_w + 1)));
			fputs(draw_colour(BUFFER_LINE_HEADER_FG_NEUTRAL, -1), stdout);
			fputs(sep, stdout);
		}

		if (*p1) {
			printf(MOVE(%d, %d), coords.r0, head_w);
			printf(C_MOVE(%d, %d), coords.r0, head_w);

			print_p1 = p1;
			print_p2 = word_wrap(text_w, &p1, p2);
			print_p2 = irc_strwrap(text_w, &p1, p2);

			fputs(draw_colour(line->text[0] == QUOTE_CHAR
					? BUFFER_LINE_TEXT_FG_GREEN


@@ 452,9 437,8 @@ draw_input(struct input *inp, struct coords coords)
	unsigned cols_t = coords.cN - coords.c0 + 1,
	         cursor = coords.c0;

	printf(RESET_ATTRIBUTES);
	printf(MOVE(%d, 1) CLEAR_LINE, coords.rN);
	printf(CURSOR_SAVE);
	printf(C_MOVE(%d, 1) CLEAR_LINE, coords.rN);
	printf(C_SAVE);

	/* Insufficient columns for meaningful input drawing */
	if (cols_t < 3)


@@ 479,29 463,32 @@ draw_input(struct input *inp, struct coords coords)
			goto print_input;
	}

	if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
			"%s", draw_colour(INPUT_FG, INPUT_BG)))
		goto print_input;
	if (action_message()) {

	if (action_message) {
		if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
				"%s", draw_colour(ACTION_FG, ACTION_BG)))
			goto print_input;

		cursor = coords.cN;

		if (!draw_fmt(&input_ptr, &buff_n, &text_n, 1,
				"%s", action_message))
		if (!draw_fmt(&input_ptr, &buff_n, &text_n, 1, "-- %s --", action_message()))
			goto print_input;

		cursor = cols_t - text_n + 1;

	} else {
		if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
				"%s", draw_colour(INPUT_FG, INPUT_BG)))
			goto print_input;

		cursor += input_frame(inp, input_ptr, text_n);
	}

print_input:

	fputs(input, stdout);
	printf(MOVE(%d, %d), coords.rN, (cursor >= coords.c0 && cursor <= coords.cN) ? cursor : coords.cN);
	printf(CURSOR_SAVE);
	printf(C_MOVE(%d, %d), coords.rN, (cursor >= coords.c0 && cursor <= coords.cN) ? cursor : coords.cN);
	printf(C_SAVE);
}

/* TODO


@@ 524,7 511,7 @@ draw_nav(struct channel *c)
	 *  - The nav is kept framed between the first and last channels
	 */

	printf(MOVE(1, 1) CLEAR_LINE);
	printf(C_MOVE(1, 1) CLEAR_LINE);

	static struct channel *frame_prev,
	                      *frame_next;


@@ 541,7 528,7 @@ draw_nav(struct channel *c)
	size_t len, total_len = 0;

	/* Bump the channel frames, if applicable */
	if ((total_len = (c->name_len + 2)) >= io_tty_cols())
	if ((total_len = (c->name_len + 2)) >= state_cols())
		return;
	else if (c == frame_prev && frame_prev != c_first)
		frame_prev = channel_get_prev(frame_prev);


@@ 560,7 547,7 @@ draw_nav(struct channel *c)
			tmp = channel_get_next(tmp_next);
			len = tmp->name_len;

			while ((total_len += (len + 2)) < io_tty_cols() && tmp != c_first) {
			while ((total_len += (len + 2)) < state_cols() && tmp != c_first) {

				tmp_next = tmp;



@@ 578,7 565,7 @@ draw_nav(struct channel *c)
			tmp = channel_get_prev(tmp_prev);
			len = tmp->name_len;

			while ((total_len += (len + 2)) < io_tty_cols() && tmp != c_last) {
			while ((total_len += (len + 2)) < state_cols() && tmp != c_last) {

				tmp_prev = tmp;



@@ 593,7 580,7 @@ draw_nav(struct channel *c)
		len = tmp->name_len;

		/* Next channel doesn't fit */
		if ((total_len += (len + 2)) >= io_tty_cols())
		if ((total_len += (len + 2)) >= state_cols())
			break;

		if (nextward)


@@ 638,19 625,17 @@ draw_status(struct channel *c)
	float sb;
	int ret;
	unsigned col = 0;
	unsigned cols = io_tty_cols();
	unsigned rows = io_tty_rows();
	unsigned cols = state_cols();
	unsigned rows = state_rows();

	/* Insufficient columns for meaningful status */
	if (cols < 3)
		return;

	printf(RESET_ATTRIBUTES);

	printf(MOVE(2, 1));
	printf(C_MOVE(2, 1));
	printf("%.*s", cols, (char *)(memset(alloca(cols), *HORIZONTAL_SEPARATOR, cols)));

	printf(MOVE(%d, 1) CLEAR_LINE, rows - 1);
	printf(C_MOVE(%d, 1) CLEAR_LINE, rows - 1);

	/* Print status to temporary buffer */
	char status_buff[cols + 1];


@@ 758,25 743,21 @@ static char*
draw_colour(int fg, int bg)
{
	/* Set terminal foreground and background colours to a value [0, 255],
	 * or reset colour if given anything else
	 *
	 * Foreground(F): ESC"[38;5;Fm"
	 * Background(B): ESC"[48;5;Bm"
	 * */
	 * or reset colour if given anything else */

	static char buf[COLOUR_SIZE + 1] = RESET_ATTRIBUTES;
	size_t len = sizeof(RESET_ATTRIBUTES) - 1;
	static char buf[COLOUR_SIZE + 1] = ATTR_RESET;
	size_t len = sizeof(ATTR_RESET) - 1;
	int ret = 0;

	if (fg >= 0 && fg <= 255) {
		if ((ret = snprintf(buf + len, sizeof(buf) - len, ESC"[38;5;%dm", fg)) < 0)
		if ((ret = snprintf(buf + len, sizeof(buf) - len, ATTR_FG(%d), fg)) < 0)
			buf[len] = 0;
		else
			len += ret;
	}

	if (bg >= 0 && bg <= 255) {
		if ((ret = snprintf(buf + len, sizeof(buf) - len, ESC"[48;5;%dm", bg)) < 0)
		if ((snprintf(buf + len, sizeof(buf) - len, ATTR_BG(%d), bg)) < 0)
			buf[len] = 0;
	}


M src/draw.h => src/draw.h +3 -4
@@ 1,5 1,5 @@
#ifndef DRAW_H
#define DRAW_H
#ifndef RIRC_DRAW_H
#define RIRC_DRAW_H

enum draw_bit
{


@@ 11,10 11,9 @@ enum draw_bit
	DRAW_NAV,    /* set bit to draw nav */
	DRAW_STATUS, /* set bit to draw status */
	DRAW_ALL,    /* set all draw bits aside from bell */
	DRAW_CLEAR,  /* clear the terminal */
};

void draw(enum draw_bit);
void draw_init(void);
void draw_term(void);

#endif

M src/handlers/irc_ctcp.c => src/handlers/irc_ctcp.c +37 -27
@@ 1,16 1,16 @@
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include "src/handlers/irc_ctcp.h"

#include "src/components/channel.h"
#include "src/components/server.h"
#include "src/handlers/irc_ctcp.gperf.out"
#include "src/handlers/irc_ctcp.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"

#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>

#define failf(S, ...) \
	do { server_error((S), __VA_ARGS__); \
	     return 1; \


@@ 22,11 22,21 @@
	         failf((S), "Send fail: %s", io_err(ret)); \
	} while (0)

#define CTCP_CLIENTINFO \
	"ACTION "     \
	"CLIENTINFO " \
	"FINGER "     \
	"PING "       \
	"SOURCE "     \
	"TIME "       \
	"USERINFO "   \
	"VERSION"

static int
parse_ctcp(struct server *s, const char *from, char **args, const char **cmd)
{
	char *message = *args;
	char *command;
	char *message = *args;
	char *p;

	if (!from)


@@ 40,14 50,14 @@ parse_ctcp(struct server *s, const char *from, char **args, const char **cmd)

	*message++ = 0;

	if (!(command = strsep(&message)))
	if (!(command = irc_strsep(&message)))
		failf(s, "Received empty CTCP from %s", from);

	for (p = command; *p; p++)
		*p = toupper(*p);

	*cmd = command;
	*args = strtrim(&message);
	*args = irc_strtrim(&message);

	return 0;
}


@@ 112,7 122,7 @@ ctcp_request_action(struct server *s, const char *from, const char *targ, char *
		failf(s, "CTCP ACTION: target '%s' not found", targ);
	}

	if (strtrim(&m))
	if (irc_strtrim(&m))
		newlinef(c, 0, "*", "%s %s", from, m);
	else
		newlinef(c, 0, "*", "%s", from);


@@ 133,12 143,12 @@ ctcp_request_clientinfo(struct server *s, const char *from, const char *targ, ch

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP CLIENTINFO from %s (%s)", from, m);
	else
		server_info(s, "CTCP CLIENTINFO from %s", from);

	sendf(s, "NOTICE %s :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001", from);
	sendf(s, "NOTICE %s :\001CLIENTINFO " CTCP_CLIENTINFO "\001", from);

	return 0;
}


@@ 157,7 167,7 @@ ctcp_request_finger(struct server *s, const char *from, const char *targ, char *

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP FINGER from %s (%s)", from, m);
	else
		server_info(s, "CTCP FINGER from %s", from);


@@ 181,7 191,7 @@ ctcp_request_ping(struct server *s, const char *from, const char *targ, char *m)

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP PING from %s (%s)", from, m);
	else
		server_info(s, "CTCP PING from %s", from);


@@ 206,12 216,12 @@ ctcp_request_source(struct server *s, const char *from, const char *targ, char *

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP SOURCE from %s (%s)", from, m);
	else
		server_info(s, "CTCP SOURCE from %s", from);

	sendf(s, "NOTICE %s :\001SOURCE rcr.io/rirc\001", from);
	sendf(s, "NOTICE %s :\001SOURCE https://rcr.io/rirc\001", from);

	return 0;
}


@@ 237,7 247,7 @@ ctcp_request_time(struct server *s, const char *from, const char *targ, char *m)

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP TIME from %s (%s)", from, m);
	else
		server_info(s, "CTCP TIME from %s", from);


@@ 274,7 284,7 @@ ctcp_request_userinfo(struct server *s, const char *from, const char *targ, char

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP USERINFO from %s (%s)", from, m);
	else
		server_info(s, "CTCP USERINFO from %s", from);


@@ 297,7 307,7 @@ ctcp_request_version(struct server *s, const char *from, const char *targ, char 

	UNUSED(targ);

	if (strtrim(&m))
	if (irc_strtrim(&m))
		server_info(s, "CTCP VERSION from %s (%s)", from, m);
	else
		server_info(s, "CTCP VERSION from %s", from);


@@ 320,7 330,7 @@ ctcp_response_clientinfo(struct server *s, const char *from, const char *targ, c

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP CLIENTINFO response from %s: empty message", from);

	server_info(s, "CTCP CLIENTINFO response from %s: %s", from, m);


@@ 342,7 352,7 @@ ctcp_response_finger(struct server *s, const char *from, const char *targ, char 

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP FINGER response from %s: empty message", from);

	server_info(s, "CTCP FINGER response from %s: %s", from, m);


@@ 374,10 384,10 @@ ctcp_response_ping(struct server *s, const char *from, const char *targ, char *m

	UNUSED(targ);

	if (!(sec = strsep(&m)))
	if (!(sec = irc_strsep(&m)))
		failf(s, "CTCP PING response from %s: sec is NULL", from);

	if (!(usec = strsep(&m)))
	if (!(usec = irc_strsep(&m)))
		failf(s, "CTCP PING response from %s: usec is NULL", from);

	for (const char *p = sec; *p; p++) {


@@ 434,7 444,7 @@ ctcp_response_source(struct server *s, const char *from, const char *targ, char 

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP SOURCE response from %s: empty message", from);

	server_info(s, "CTCP SOURCE response from %s: %s", from, m);


@@ 458,7 468,7 @@ ctcp_response_time(struct server *s, const char *from, const char *targ, char *m

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP TIME response from %s: empty message", from);

	server_info(s, "CTCP TIME response from %s: %s", from, m);


@@ 480,7 490,7 @@ ctcp_response_userinfo(struct server *s, const char *from, const char *targ, cha

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP USERINFO response from %s: empty message", from);

	server_info(s, "CTCP USERINFO response from %s: %s", from, m);


@@ 501,7 511,7 @@ ctcp_response_version(struct server *s, const char *from, const char *targ, char

	UNUSED(targ);

	if (!strtrim(&m))
	if (!irc_strtrim(&m))
		failf(s, "CTCP VERSION response from %s: empty message", from);

	server_info(s, "CTCP VERSION response from %s: %s", from, m);

M src/handlers/irc_ctcp.h => src/handlers/irc_ctcp.h +4 -2
@@ 1,5 1,7 @@
#ifndef IRC_CTCP_H
#define IRC_CTCP_H
#ifndef RIRC_HANDLERS_IRC_CTCP_H
#define RIRC_HANDLERS_IRC_CTCP_H

#include "src/components/server.h"

/* Summary of CTCP implementation:
 *

M src/handlers/irc_recv.c => src/handlers/irc_recv.c +461 -330
@@ 1,28 1,19 @@
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include "src/handlers/irc_recv.h"

#include "src/components/server.h"
#include "src/handlers/irc_ctcp.h"
#include "src/handlers/irc_recv.gperf.out"
#include "src/handlers/irc_recv.h"
#include "src/handlers/ircv3.h"
#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"

#ifndef JOIN_THRESHOLD
#define JOIN_THRESHOLD 0
#endif
#include "config.h"

#ifndef PART_THRESHOLD
#define PART_THRESHOLD 0
#endif

#ifndef QUIT_THRESHOLD
#define QUIT_THRESHOLD 0
#endif
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>

#define failf(S, ...) \
	do { server_error((S), __VA_ARGS__); \


@@ 35,183 26,189 @@
	         failf((S), "Send fail: %s", io_err(ret)); \
	} while (0)

/* Default message handler */
static int irc_message(struct server*, struct irc_message*, const char*);
/* Generic message handler */
static int irc_generic(struct server*, struct irc_message*, const char*, const char*);

/* Generic handlers */
static int irc_error(struct server*, struct irc_message*);
static int irc_ignore(struct server*, struct irc_message*);
static int irc_info(struct server*, struct irc_message*);
/* Generic message handler subtypes */
static int irc_generic_error(struct server*, struct irc_message*);
static int irc_generic_ignore(struct server*, struct irc_message*);
static int irc_generic_info(struct server*, struct irc_message*);
static int irc_generic_unknown(struct server*, struct irc_message*);

/* Numeric handlers */
static int irc_001(struct server*, struct irc_message*);
static int irc_004(struct server*, struct irc_message*);
static int irc_005(struct server*, struct irc_message*);
static int irc_221(struct server*, struct irc_message*);
static int irc_324(struct server*, struct irc_message*);
static int irc_328(struct server*, struct irc_message*);
static int irc_329(struct server*, struct irc_message*);
static int irc_332(struct server*, struct irc_message*);
static int irc_333(struct server*, struct irc_message*);
static int irc_353(struct server*, struct irc_message*);
static int irc_433(struct server*, struct irc_message*);
static int irc_numeric_001(struct server*, struct irc_message*);
static int irc_numeric_004(struct server*, struct irc_message*);
static int irc_numeric_005(struct server*, struct irc_message*);
static int irc_numeric_221(struct server*, struct irc_message*);
static int irc_numeric_324(struct server*, struct irc_message*);
static int irc_numeric_328(struct server*, struct irc_message*);
static int irc_numeric_329(struct server*, struct irc_message*);
static int irc_numeric_332(struct server*, struct irc_message*);
static int irc_numeric_333(struct server*, struct irc_message*);
static int irc_numeric_353(struct server*, struct irc_message*);
static int irc_numeric_433(struct server*, struct irc_message*);

static int irc_recv_numeric(struct server*, struct irc_message*);
static int recv_mode_chanmodes(struct irc_message*, const struct mode_cfg*, struct server*, struct channel*);
static int recv_mode_usermodes(struct irc_message*, const struct mode_cfg*, struct server*);

static const unsigned quit_threshold = QUIT_THRESHOLD;
static const unsigned join_threshold = JOIN_THRESHOLD;
static const unsigned part_threshold = PART_THRESHOLD;
static unsigned quit_threshold    = FILTER_THRESHOLD_QUIT;
static unsigned join_threshold    = FILTER_THRESHOLD_JOIN;
static unsigned part_threshold    = FILTER_THRESHOLD_PART;
static unsigned account_threshold = FILTER_THRESHOLD_ACCOUNT;
static unsigned away_threshold    = FILTER_THRESHOLD_AWAY;
static unsigned chghost_threshold = FILTER_THRESHOLD_CHGHOST;

static const irc_recv_f irc_numerics[] = {
	  [1] = irc_001,    /* RPL_WELCOME */
	  [2] = irc_info,   /* RPL_YOURHOST */
	  [3] = irc_info,   /* RPL_CREATED */
	  [4] = irc_004,    /* RPL_MYINFO */
	  [5] = irc_005,    /* RPL_ISUPPORT */
	[200] = irc_info,   /* RPL_TRACELINK */
	[201] = irc_info,   /* RPL_TRACECONNECTING */
	[202] = irc_info,   /* RPL_TRACEHANDSHAKE */
	[203] = irc_info,   /* RPL_TRACEUNKNOWN */
	[204] = irc_info,   /* RPL_TRACEOPERATOR */
	[205] = irc_info,   /* RPL_TRACEUSER */
	[206] = irc_info,   /* RPL_TRACESERVER */
	[207] = irc_info,   /* RPL_TRACESERVICE */
	[208] = irc_info,   /* RPL_TRACENEWTYPE */
	[209] = irc_info,   /* RPL_TRACECLASS */
	[210] = irc_info,   /* RPL_TRACELOG */
	[211] = irc_info,   /* RPL_STATSLINKINFO */
	[212] = irc_info,   /* RPL_STATSCOMMANDS */
	[213] = irc_info,   /* RPL_STATSCLINE */
	[214] = irc_info,   /* RPL_STATSNLINE */
	[215] = irc_info,   /* RPL_STATSILINE */
	[216] = irc_info,   /* RPL_STATSKLINE */
	[217] = irc_info,   /* RPL_STATSQLINE */
	[218] = irc_info,   /* RPL_STATSYLINE */
	[219] = irc_ignore, /* RPL_ENDOFSTATS */
	[221] = irc_221,    /* RPL_UMODEIS */
	[234] = irc_info,   /* RPL_SERVLIST */
	[235] = irc_ignore, /* RPL_SERVLISTEND */
	[240] = irc_info,   /* RPL_STATSVLINE */
	[241] = irc_info,   /* RPL_STATSLLINE */
	[242] = irc_info,   /* RPL_STATSUPTIME */
	[243] = irc_info,   /* RPL_STATSOLINE */
	[244] = irc_info,   /* RPL_STATSHLINE */
	[245] = irc_info,   /* RPL_STATSSLINE */
	[246] = irc_info,   /* RPL_STATSPING */
	[247] = irc_info,   /* RPL_STATSBLINE */
	[250] = irc_info,   /* RPL_STATSCONN */
	[251] = irc_info,   /* RPL_LUSERCLIENT */
	[252] = irc_info,   /* RPL_LUSEROP */
	[253] = irc_info,   /* RPL_LUSERUNKNOWN */
	[254] = irc_info,   /* RPL_LUSERCHANNELS */
	[255] = irc_info,   /* RPL_LUSERME */
	[256] = irc_info,   /* RPL_ADMINME */
	[257] = irc_info,   /* RPL_ADMINLOC1 */
	[258] = irc_info,   /* RPL_ADMINLOC2 */
	[259] = irc_info,   /* RPL_ADMINEMAIL */
	[262] = irc_info,   /* RPL_TRACEEND */
	[263] = irc_info,   /* RPL_TRYAGAIN */
	[265] = irc_info,   /* RPL_LOCALUSERS */
	[266] = irc_info,   /* RPL_GLOBALUSERS */
	[301] = irc_info,   /* RPL_AWAY */
	[302] = irc_info,   /* ERR_USERHOST */
	[303] = irc_info,   /* RPL_ISON */
	[305] = irc_info,   /* RPL_UNAWAY */
	[306] = irc_info,   /* RPL_NOWAWAY */
	[311] = irc_info,   /* RPL_WHOISUSER */
	[312] = irc_info,   /* RPL_WHOISSERVER */
	[313] = irc_info,   /* RPL_WHOISOPERATOR */
	[314] = irc_info,   /* RPL_WHOWASUSER */
	[315] = irc_ignore, /* RPL_ENDOFWHO */
	[317] = irc_info,   /* RPL_WHOISIDLE */
	[318] = irc_ignore, /* RPL_ENDOFWHOIS */
	[319] = irc_info,   /* RPL_WHOISCHANNELS */
	[322] = irc_info,   /* RPL_LIST */
	[323] = irc_ignore, /* RPL_LISTEND */
	[324] = irc_324,    /* RPL_CHANNELMODEIS */
	[325] = irc_info,   /* RPL_UNIQOPIS */
	[328] = irc_328,    /* RPL_CHANNEL_URL */
	[329] = irc_329,    /* RPL_CREATIONTIME */
	[331] = irc_ignore, /* RPL_NOTOPIC */
	[332] = irc_332,    /* RPL_TOPIC */
	[333] = irc_333,    /* RPL_TOPICWHOTIME */
	[341] = irc_info,   /* RPL_INVITING */
	[346] = irc_info,   /* RPL_INVITELIST */
	[347] = irc_ignore, /* RPL_ENDOFINVITELIST */
	[348] = irc_info,   /* RPL_EXCEPTLIST */
	[349] = irc_ignore, /* RPL_ENDOFEXCEPTLIST */
	[351] = irc_info,   /* RPL_VERSION */
	[352] = irc_info,   /* RPL_WHOREPLY */
	[353] = irc_353,    /* RPL_NAMEREPLY */
	[364] = irc_info,   /* RPL_LINKS */
	[365] = irc_ignore, /* RPL_ENDOFLINKS */
	[366] = irc_ignore, /* RPL_ENDOFNAMES */
	[367] = irc_info,   /* RPL_BANLIST */
	[368] = irc_ignore, /* RPL_ENDOFBANLIST */
	[369] = irc_ignore, /* RPL_ENDOFWHOWAS */
	[371] = irc_info,   /* RPL_INFO */
	[372] = irc_info,   /* RPL_MOTD */
	[374] = irc_ignore, /* RPL_ENDOFINFO */
	[375] = irc_ignore, /* RPL_MOTDSTART */
	[376] = irc_ignore, /* RPL_ENDOFMOTD */
	[381] = irc_info,   /* RPL_YOUREOPER */
	[391] = irc_info,   /* RPL_TIME */
	[401] = irc_error,  /* ERR_NOSUCHNICK */
	[402] = irc_error,  /* ERR_NOSUCHSERVER */
	[403] = irc_error,  /* ERR_NOSUCHCHANNEL */
	[404] = irc_error,  /* ERR_CANNOTSENDTOCHAN */
	[405] = irc_error,  /* ERR_TOOMANYCHANNELS */
	[406] = irc_error,  /* ERR_WASNOSUCHNICK */
	[407] = irc_error,  /* ERR_TOOMANYTARGETS */
	[408] = irc_error,  /* ERR_NOSUCHSERVICE */
	[409] = irc_error,  /* ERR_NOORIGIN */
	[410] = irc_error,  /* ERR_INVALIDCAPCMD */
	[411] = irc_error,  /* ERR_NORECIPIENT */
	[412] = irc_error,  /* ERR_NOTEXTTOSEND */
	[413] = irc_error,  /* ERR_NOTOPLEVEL */
	[414] = irc_error,  /* ERR_WILDTOPLEVEL */
	[415] = irc_error,  /* ERR_BADMASK */
	[416] = irc_error,  /* ERR_TOOMANYMATCHES */
	[421] = irc_error,  /* ERR_UNKNOWNCOMMAND */
	[422] = irc_error,  /* ERR_NOMOTD */
	[423] = irc_error,  /* ERR_NOADMININFO */
	[431] = irc_error,  /* ERR_NONICKNAMEGIVEN */
	[432] = irc_error,  /* ERR_ERRONEUSNICKNAME */
	[433] = irc_433,    /* ERR_NICKNAMEINUSE */
	[436] = irc_error,  /* ERR_NICKCOLLISION */
	[437] = irc_error,  /* ERR_UNAVAILRESOURCE */
	[441] = irc_error,  /* ERR_USERNOTINCHANNEL */
	[442] = irc_error,  /* ERR_NOTONCHANNEL */
	[443] = irc_error,  /* ERR_USERONCHANNEL */
	[451] = irc_error,  /* ERR_NOTREGISTERED */
	[461] = irc_error,  /* ERR_NEEDMOREPARAMS */
	[462] = irc_error,  /* ERR_ALREADYREGISTRED */
	[463] = irc_error,  /* ERR_NOPERMFORHOST */
	[464] = irc_error,  /* ERR_PASSWDMISMATCH */
	[465] = irc_error,  /* ERR_YOUREBANNEDCREEP */
	[466] = irc_error,  /* ERR_YOUWILLBEBANNED */
	[467] = irc_error,  /* ERR_KEYSET */
	[471] = irc_error,  /* ERR_CHANNELISFULL */
	[472] = irc_error,  /* ERR_UNKNOWNMODE */
	[473] = irc_error,  /* ERR_INVITEONLYCHAN */
	[474] = irc_error,  /* ERR_BANNEDFROMCHAN */
	[475] = irc_error,  /* ERR_BADCHANNELKEY */
	[476] = irc_error,  /* ERR_BADCHANMASK */
	[477] = irc_error,  /* ERR_NOCHANMODES */
	[478] = irc_error,  /* ERR_BANLISTFULL */
	[481] = irc_error,  /* ERR_NOPRIVILEGES */
	[482] = irc_error,  /* ERR_CHANOPRIVSNEEDED */
	[483] = irc_error,  /* ERR_CANTKILLSERVER */
	[484] = irc_error,  /* ERR_RESTRICTED */
	[485] = irc_error,  /* ERR_UNIQOPPRIVSNEEDED */
	[491] = irc_error,  /* ERR_NOOPERHOST */
	[501] = irc_error,  /* ERR_UMODEUNKNOWNFLAG */
	[502] = irc_error,  /* ERR_USERSDONTMATCH */
	[704] = irc_info,   /* RPL_HELPSTART */
	[705] = irc_info,   /* RPL_HELP */
	[706] = irc_ignore, /* RPL_ENDOFHELP */
	  [1] = irc_numeric_001,    /* RPL_WELCOME */
	  [2] = irc_generic_info,   /* RPL_YOURHOST */
	  [3] = irc_generic_info,   /* RPL_CREATED */
	  [4] = irc_numeric_004,    /* RPL_MYINFO */
	  [5] = irc_numeric_005,    /* RPL_ISUPPORT */
	[200] = irc_generic_info,   /* RPL_TRACELINK */
	[201] = irc_generic_info,   /* RPL_TRACECONNECTING */
	[202] = irc_generic_info,   /* RPL_TRACEHANDSHAKE */
	[203] = irc_generic_info,   /* RPL_TRACEUNKNOWN */
	[204] = irc_generic_info,   /* RPL_TRACEOPERATOR */
	[205] = irc_generic_info,   /* RPL_TRACEUSER */
	[206] = irc_generic_info,   /* RPL_TRACESERVER */
	[207] = irc_generic_info,   /* RPL_TRACESERVICE */
	[208] = irc_generic_info,   /* RPL_TRACENEWTYPE */
	[209] = irc_generic_info,   /* RPL_TRACECLASS */
	[210] = irc_generic_info,   /* RPL_TRACELOG */
	[211] = irc_generic_info,   /* RPL_STATSLINKINFO */
	[212] = irc_generic_info,   /* RPL_STATSCOMMANDS */
	[213] = irc_generic_info,   /* RPL_STATSCLINE */
	[214] = irc_generic_info,   /* RPL_STATSNLINE */
	[215] = irc_generic_info,   /* RPL_STATSILINE */
	[216] = irc_generic_info,   /* RPL_STATSKLINE */
	[217] = irc_generic_info,   /* RPL_STATSQLINE */
	[218] = irc_generic_info,   /* RPL_STATSYLINE */
	[219] = irc_generic_ignore, /* RPL_ENDOFSTATS */
	[221] = irc_numeric_221,    /* RPL_UMODEIS */
	[234] = irc_generic_info,   /* RPL_SERVLIST */
	[235] = irc_generic_ignore, /* RPL_SERVLISTEND */
	[240] = irc_generic_info,   /* RPL_STATSVLINE */
	[241] = irc_generic_info,   /* RPL_STATSLLINE */
	[242] = irc_generic_info,   /* RPL_STATSUPTIME */
	[243] = irc_generic_info,   /* RPL_STATSOLINE */
	[244] = irc_generic_info,   /* RPL_STATSHLINE */
	[245] = irc_generic_info,   /* RPL_STATSSLINE */
	[246] = irc_generic_info,   /* RPL_STATSPING */
	[247] = irc_generic_info,   /* RPL_STATSBLINE */
	[250] = irc_generic_info,   /* RPL_STATSCONN */
	[251] = irc_generic_info,   /* RPL_LUSERCLIENT */
	[252] = irc_generic_info,   /* RPL_LUSEROP */
	[253] = irc_generic_info,   /* RPL_LUSERUNKNOWN */
	[254] = irc_generic_info,   /* RPL_LUSERCHANNELS */
	[255] = irc_generic_info,   /* RPL_LUSERME */
	[256] = irc_generic_info,   /* RPL_ADMINME */
	[257] = irc_generic_info,   /* RPL_ADMINLOC1 */
	[258] = irc_generic_info,   /* RPL_ADMINLOC2 */
	[259] = irc_generic_info,   /* RPL_ADMINEMAIL */
	[262] = irc_generic_info,   /* RPL_TRACEEND */
	[263] = irc_generic_info,   /* RPL_TRYAGAIN */
	[265] = irc_generic_info,   /* RPL_LOCALUSERS */
	[266] = irc_generic_info,   /* RPL_GLOBALUSERS */
	[301] = irc_generic_info,   /* RPL_AWAY */
	[302] = irc_generic_info,   /* ERR_USERHOST */
	[303] = irc_generic_info,   /* RPL_ISON */
	[305] = irc_generic_info,   /* RPL_UNAWAY */
	[306] = irc_generic_info,   /* RPL_NOWAWAY */
	[311] = irc_generic_info,   /* RPL_WHOISUSER */
	[312] = irc_generic_info,   /* RPL_WHOISSERVER */
	[313] = irc_generic_info,   /* RPL_WHOISOPERATOR */
	[314] = irc_generic_info,   /* RPL_WHOWASUSER */
	[315] = irc_generic_ignore, /* RPL_ENDOFWHO */
	[317] = irc_generic_info,   /* RPL_WHOISIDLE */
	[318] = irc_generic_ignore, /* RPL_ENDOFWHOIS */
	[319] = irc_generic_info,   /* RPL_WHOISCHANNELS */
	[322] = irc_generic_info,   /* RPL_LIST */
	[323] = irc_generic_ignore, /* RPL_LISTEND */
	[324] = irc_numeric_324,    /* RPL_CHANNELMODEIS */
	[325] = irc_generic_info,   /* RPL_UNIQOPIS */
	[328] = irc_numeric_328,    /* RPL_CHANNEL_URL */
	[329] = irc_numeric_329,    /* RPL_CREATIONTIME */
	[331] = irc_generic_ignore, /* RPL_NOTOPIC */
	[332] = irc_numeric_332,    /* RPL_TOPIC */
	[333] = irc_numeric_333,    /* RPL_TOPICWHOTIME */
	[341] = irc_generic_info,   /* RPL_INVITING */
	[346] = irc_generic_info,   /* RPL_INVITELIST */
	[347] = irc_generic_ignore, /* RPL_ENDOFINVITELIST */
	[348] = irc_generic_info,   /* RPL_EXCEPTLIST */
	[349] = irc_generic_ignore, /* RPL_ENDOFEXCEPTLIST */
	[351] = irc_generic_info,   /* RPL_VERSION */
	[352] = irc_generic_info,   /* RPL_WHOREPLY */
	[353] = irc_numeric_353,    /* RPL_NAMEREPLY */
	[364] = irc_generic_info,   /* RPL_LINKS */
	[365] = irc_generic_ignore, /* RPL_ENDOFLINKS */
	[366] = irc_generic_ignore, /* RPL_ENDOFNAMES */
	[367] = irc_generic_info,   /* RPL_BANLIST */
	[368] = irc_generic_ignore, /* RPL_ENDOFBANLIST */
	[369] = irc_generic_ignore, /* RPL_ENDOFWHOWAS */
	[371] = irc_generic_info,   /* RPL_INFO */
	[372] = irc_generic_info,   /* RPL_MOTD */
	[374] = irc_generic_ignore, /* RPL_ENDOFINFO */
	[375] = irc_generic_ignore, /* RPL_MOTDSTART */
	[376] = irc_generic_ignore, /* RPL_ENDOFMOTD */
	[381] = irc_generic_info,   /* RPL_YOUREOPER */
	[391] = irc_generic_info,   /* RPL_TIME */
	[396] = irc_generic_info,   /* RPL_VISIBLEHOST */
	[401] = irc_generic_error,  /* ERR_NOSUCHNICK */
	[402] = irc_generic_error,  /* ERR_NOSUCHSERVER */
	[403] = irc_generic_error,  /* ERR_NOSUCHCHANNEL */
	[404] = irc_generic_error,  /* ERR_CANNOTSENDTOCHAN */
	[405] = irc_generic_error,  /* ERR_TOOMANYCHANNELS */
	[406] = irc_generic_error,  /* ERR_WASNOSUCHNICK */
	[407] = irc_generic_error,  /* ERR_TOOMANYTARGETS */
	[408] = irc_generic_error,  /* ERR_NOSUCHSERVICE */
	[409] = irc_generic_error,  /* ERR_NOORIGIN */
	[410] = irc_generic_error,  /* ERR_INVALIDCAPCMD */
	[411] = irc_generic_error,  /* ERR_NORECIPIENT */
	[412] = irc_generic_error,  /* ERR_NOTEXTTOSEND */
	[413] = irc_generic_error,  /* ERR_NOTOPLEVEL */
	[414] = irc_generic_error,  /* ERR_WILDTOPLEVEL */
	[415] = irc_generic_error,  /* ERR_BADMASK */
	[416] = irc_generic_error,  /* ERR_TOOMANYMATCHES */
	[421] = irc_generic_error,  /* ERR_UNKNOWNCOMMAND */
	[422] = irc_generic_error,  /* ERR_NOMOTD */
	[423] = irc_generic_error,  /* ERR_NOADMININFO */
	[431] = irc_generic_error,  /* ERR_NONICKNAMEGIVEN */
	[432] = irc_generic_error,  /* ERR_ERRONEUSNICKNAME */
	[433] = irc_numeric_433,    /* ERR_NICKNAMEINUSE */
	[436] = irc_generic_error,  /* ERR_NICKCOLLISION */
	[437] = irc_generic_error,  /* ERR_UNAVAILRESOURCE */
	[441] = irc_generic_error,  /* ERR_USERNOTINCHANNEL */
	[442] = irc_generic_error,  /* ERR_NOTONCHANNEL */
	[443] = irc_generic_error,  /* ERR_USERONCHANNEL */
	[451] = irc_generic_error,  /* ERR_NOTREGISTERED */
	[461] = irc_generic_error,  /* ERR_NEEDMOREPARAMS */
	[462] = irc_generic_error,  /* ERR_ALREADYREGISTRED */
	[463] = irc_generic_error,  /* ERR_NOPERMFORHOST */
	[464] = irc_generic_error,  /* ERR_PASSWDMISMATCH */
	[465] = irc_generic_error,  /* ERR_YOUREBANNEDCREEP */
	[466] = irc_generic_error,  /* ERR_YOUWILLBEBANNED */
	[467] = irc_generic_error,  /* ERR_KEYSET */
	[471] = irc_generic_error,  /* ERR_CHANNELISFULL */
	[472] = irc_generic_error,  /* ERR_UNKNOWNMODE */
	[473] = irc_generic_error,  /* ERR_INVITEONLYCHAN */
	[474] = irc_generic_error,  /* ERR_BANNEDFROMCHAN */
	[475] = irc_generic_error,  /* ERR_BADCHANNELKEY */
	[476] = irc_generic_error,  /* ERR_BADCHANMASK */
	[477] = irc_generic_error,  /* ERR_NOCHANMODES */
	[478] = irc_generic_error,  /* ERR_BANLISTFULL */
	[481] = irc_generic_error,  /* ERR_NOPRIVILEGES */
	[482] = irc_generic_error,  /* ERR_CHANOPRIVSNEEDED */
	[483] = irc_generic_error,  /* ERR_CANTKILLSERVER */
	[484] = irc_generic_error,  /* ERR_RESTRICTED */
	[485] = irc_generic_error,  /* ERR_UNIQOPPRIVSNEEDED */
	[491] = irc_generic_error,  /* ERR_NOOPERHOST */
	[501] = irc_generic_error,  /* ERR_UMODEUNKNOWNFLAG */
	[502] = irc_generic_error,  /* ERR_USERSDONTMATCH */
	[704] = irc_generic_info,   /* RPL_HELPSTART */
	[705] = irc_generic_info,   /* RPL_HELP */
	[706] = irc_generic_ignore, /* RPL_ENDOFHELP */
	[1000] = NULL               /* Out of range */
};

int


@@ 225,39 222,59 @@ irc_recv(struct server *s, struct irc_message *m)
	if ((handler = recv_handler_lookup(m->command, m->len_command)))
		return handler->f(s, m);

	return irc_message(s, m, FROM_UNKNOWN);
	return irc_generic_unknown(s, m);
}

static int
irc_message(struct server *s, struct irc_message *m, const char *from)
irc_generic(struct server *s, struct irc_message *m, const char *command, const char *from)
{
	char *trailing;
	/* Generic handling of messages, in the form:
	 *
	 *   [command] [params] ~ trailing
	 */

	if (!strtrim(&m->params))
		return 0;
	const char *params       = NULL;
	const char *params_sep   = NULL;
	const char *trailing     = NULL;
	const char *trailing_sep = NULL;

	if (irc_message_split(m, &trailing)) {
		if (m->params)
			newlinef(s->channel, 0, from, "[%s] ~ %s", m->params, trailing);
		else
			newlinef(s->channel, 0, from, "%s", trailing);
	} else if (m->params) {
		newlinef(s->channel, 0, from, "[%s]", m->params);
	}
	if (!command && !irc_strtrim(&m->params))
		return 1;

	irc_message_split(m, &params, &trailing);

	if (command && params)
		params_sep = " ";

	if ((command || params) && trailing)
		trailing_sep = " ~ ";

	newlinef(s->channel, 0, from,
		"%s%s%s" "%s%s%s%s" "%s%s",
		(!command      ? "" : "["),
		(!command      ? "" : command),
		(!command      ? "" : "]"),
		(!params_sep   ? "" : params_sep),
		(!params       ? "" : "["),
		(!params       ? "" : params),
		(!params       ? "" : "]"),
		(!trailing_sep ? "" : trailing_sep),
		(!trailing     ? "" : trailing)
	);

	return 0;
}

static int
irc_error(struct server *s, struct irc_message *m)
irc_generic_error(struct server *s, struct irc_message *m)
{
	/* Generic error message handling */

	return irc_message(s, m, FROM_ERROR);
	return irc_generic(s, m, NULL, FROM_ERROR);
}

static int
irc_ignore(struct server *s, struct irc_message *m)
irc_generic_ignore(struct server *s, struct irc_message *m)
{
	/* Generic handling for ignored message types */



@@ 268,19 285,28 @@ irc_ignore(struct server *s, struct irc_message *m)
}

static int
irc_info(struct server *s, struct irc_message *m)
irc_generic_info(struct server *s, struct irc_message *m)
{
	/* Generic info message handling */

	return irc_message(s, m, FROM_INFO);
	return irc_generic(s, m, NULL, FROM_INFO);
}

static int
irc_001(struct server *s, struct irc_message *m)
irc_generic_unknown(struct server *s, struct irc_message *m)
{
	/* Generic handling of unknown commands */

	return irc_generic(s, m, m->command, FROM_UNKNOWN);
}

static int
irc_numeric_001(struct server *s, struct irc_message *m)
{
	/* 001 :<Welcome message> */

	char *trailing;
	const char *params;
	const char *trailing;
	struct channel *c = s->channel;

	s->registered = 1;


@@ 290,8 316,8 @@ irc_001(struct server *s, struct irc_message *m)
			sendf(s, "JOIN %s", c->name);
	} while ((c = c->next) != s->channel);

	if (irc_message_split(m, &trailing))
		newline(s->channel, 0, FROM_INFO, trailing);
	if (irc_message_split(m, &params, &trailing))
		newlinef(s->channel, 0, FROM_INFO, "%s", trailing);

	server_info(s, "You are known as %s", s->nick);



@@ 299,16 325,17 @@ irc_001(struct server *s, struct irc_message *m)
}

static int
irc_004(struct server *s, struct irc_message *m)
irc_numeric_004(struct server *s, struct irc_message *m)
{
	/* 004 1*<params> [:message] */

	char *trailing;
	const char *params;
	const char *trailing;

	if (irc_message_split(m, &trailing))
		newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", m->params, trailing);
	if (irc_message_split(m, &params, &trailing))
		newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", params, trailing);
	else
		newlinef(s->channel, 0, FROM_INFO, "%s", m->params);
		newlinef(s->channel, 0, FROM_INFO, "%s", params);

	server_set_004(s, m->params);



@@ 316,16 343,17 @@ irc_004(struct server *s, struct irc_message *m)
}

static int
irc_005(struct server *s, struct irc_message *m)
irc_numeric_005(struct server *s, struct irc_message *m)
{
	/* 005 1*<params> [:message] */

	char *trailing;
	const char *params;
	const char *trailing;

	if (irc_message_split(m, &trailing))
		newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", m->params, trailing);
	if (irc_message_split(m, &params, &trailing))
		newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", params, trailing);
	else
		newlinef(s->channel, 0, FROM_INFO, "%s ~ are supported by this server", m->params);
		newlinef(s->channel, 0, FROM_INFO, "%s ~ are supported by this server", params);

	server_set_005(s, m->params);



@@ 333,7 361,7 @@ irc_005(struct server *s, struct irc_message *m)
}

static int
irc_221(struct server *s, struct irc_message *m)
irc_numeric_221(struct server *s, struct irc_message *m)
{
	/* 221 <modestring> */



@@ 341,7 369,7 @@ irc_221(struct server *s, struct irc_message *m)
}

static int
irc_324(struct server *s, struct irc_message *m)
irc_numeric_324(struct server *s, struct irc_message *m)
{
	/* 324 <channel> 1*[<modestring> [<mode arguments>]] */



@@ 358,7 386,7 @@ irc_324(struct server *s, struct irc_message *m)
}

static int
irc_328(struct server *s, struct irc_message *m)
irc_numeric_328(struct server *s, struct irc_message *m)
{
	/* 328 <channel> <url> */



@@ 381,7 409,7 @@ irc_328(struct server *s, struct irc_message *m)
}

static int
irc_329(struct server *s, struct irc_message *m)
irc_numeric_329(struct server *s, struct irc_message *m)
{
	char buf[sizeof("1970-01-01T00:00:00")];
	char *chan;


@@ 417,9 445,9 @@ irc_329(struct server *s, struct irc_message *m)
}

static int
irc_332(struct server *s, struct irc_message *m)
irc_numeric_332(struct server *s, struct irc_message *m)
{
	/* 332 <channel> <topic> */
	/* 332 <channel> :<topic> */

	char *chan;
	char *topic;


@@ 440,7 468,7 @@ irc_332(struct server *s, struct irc_message *m)
}

static int
irc_333(struct server *s, struct irc_message *m)
irc_numeric_333(struct server *s, struct irc_message *m)
{
	/* 333 <channel> <nick> <time> */



@@ 482,13 510,14 @@ irc_333(struct server *s, struct irc_message *m)
}

static int
irc_353(struct server *s, struct irc_message *m)
irc_numeric_353(struct server *s, struct irc_message *m)
{
	/* 353 <nick> <type> <channel> 1*(<modes><nick>) */

	char *chan;
	char *nick;
	char *nicks;
	char *prfx;
	char *type;
	struct channel *c;



@@ 507,29 536,18 @@ irc_353(struct server *s, struct irc_message *m)
	if (mode_chanmode_prefix(&(c->chanmodes), &(s->mode_cfg), *type) != MODE_ERR_NONE)
		failf(s, "RPL_NAMEREPLY: invalid channel flag: '%c'", *type);

	if ((nick = strsep(&nicks))) {
		do {
			char prefix = 0;
			struct mode m = MODE_EMPTY;

			do {
				if (irc_isnickchar(*nick, 1))
					break;

				prefix = *nick++;
	while ((prfx = nick = irc_strsep(&nicks))) {

				if (mode_prfxmode_prefix(&m, &(s->mode_cfg), prefix) != MODE_ERR_NONE)
					failf(s, "RPL_NAMEREPLY: invalid user prefix: '%c'", prefix);
		struct mode m = MODE_EMPTY;

			} while (s->ircv3_caps.multi_prefix.set);
		while (mode_prfxmode_prefix(&m, &(s->mode_cfg), *nick) == MODE_ERR_NONE)
			nick++;

			if (!irc_isnick(nick))
				failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", nick);
		if (*nick == 0)
			failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prfx);

			if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
				failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);

		} while ((nick = strsep(&nicks)));
		if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
			failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);
	}

	draw(DRAW_STATUS);


@@ 538,7 556,7 @@ irc_353(struct server *s, struct irc_message *m)
}

static int
irc_433(struct server *s, struct irc_message *m)
irc_numeric_433(struct server *s, struct irc_message *m)
{
	/* 433 <nick> :Nickname is already in use */



@@ 564,55 582,37 @@ irc_recv_numeric(struct server *s, struct irc_message *m)
	/* :server <code> <target> [args] */

	char *targ;
	int code = 0;
	irc_recv_f handler = NULL;

	for (const char *p = m->command; *p; p++) {

		if (!isdigit(*p))
			failf(s, "NUMERIC: invalid");

		code *= 10;
		code += *p - '0';

		if (code > 999)
			failf(s, "NUMERIC: out of range");
	unsigned code = 0;

	if ((m->command[0] && isdigit(m->command[0]))
	 && (m->command[1] && isdigit(m->command[1]))
	 && (m->command[2] && isdigit(m->command[2]))
	 && (m->command[3] == 0))
	{
		code += (m->command[0] - '0') * 100;
		code += (m->command[1] - '0') * 10;
		code += (m->command[2] - '0');
	}

	/* Message target is only used to establish s->nick when registering with a server */
	if (!(irc_message_param(m, &targ))) {
		io_dx(s->connection);
		failf(s, "NUMERIC: target is null");
	}

	/* Message target should match s->nick or '*' if unregistered, otherwise out of sync */
	if (strcmp(targ, s->nick) && strcmp(targ, "*") && code != 1) {
		io_dx(s->connection);
		failf(s, "NUMERIC: target mismatched, nick is '%s', received '%s'", s->nick, targ);
	}
	if (!code)
		failf(s, "NUMERIC: '%s' invalid", m->command);

	if (ARR_ELEM(irc_numerics, code))
		handler = irc_numerics[code];
	if (!(irc_message_param(m, &targ)))
		failf(s, "NUMERIC: target is null");

	if (handler)
		return (*handler)(s, m);
	if (strcmp(targ, s->nick) && strcmp(targ, "*"))
		failf(s, "NUMERIC: target '%s' is invalid", targ);

	if (m->params)
		failf(s, "Numeric type '%u' unknown: %s", code, m->params);
	else
		failf(s, "Numeric type '%u' unknown", code);
}
	if (!irc_numerics[code])
		return irc_generic_unknown(s, m);

static int
recv_cap(struct server *s, struct irc_message *m)
{
	return ircv3_recv_CAP(s, m);
	return (*irc_numerics[code])(s, m);
}

static int
recv_error(struct server *s, struct irc_message *m)
{
	/* ERROR <message> */
	/* ERROR :<message> */

	char *message;



@@ 631,20 631,27 @@ recv_invite(struct server *s, struct irc_message *m)

	char *chan;
	char *nick;
	struct channel *c;

	if (!m->from)
		failf(s, "INVITE: sender's nick is null");

	if (!irc_message_param(m, &nick))
		failf(s, "INVITE: nick is null");

	if (!irc_message_param(m, &chan))
		failf(s, "INVITE: target channel is null");
		failf(s, "INVITE: channel is null");

	if (!irc_message_param(m, &nick))
		failf(s, "INVITE: target nick is null");
	if (!strcmp(nick, s->nick)) {
		newlinef(s->channel, 0, FROM_INFO, "%s invited you to %s", m->from, chan);
		return 0;
	}

	if (!strcmp(nick, s->nick))
		newlinef(s->channel, 0, FROM_INFO, "You invited %s to %s", nick, chan);
	else
		newlinef(s->channel, 0, FROM_INFO, "You've been invited to %s by %s", chan, m->from);
	/* IRCv3 CAP invite-notify, sent to all users on the target channel */
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "INVITE: channel '%s' not found", chan);

	newlinef(c, 0, FROM_INFO, "%s invited %s to %s", m->from, nick, chan);

	return 0;
}


@@ 652,7 659,8 @@ recv_invite(struct server *s, struct irc_message *m)
static int
recv_join(struct server *s, struct irc_message *m)
{
	/* :nick!user@host JOIN <channel> */
	/* :nick!user@host JOIN <channel>
	 * :nick!user@host JOIN <channel> <account> :<realname> */

	char *chan;
	struct channel *c;


@@ 661,7 669,7 @@ recv_join(struct server *s, struct irc_message *m)
		failf(s, "JOIN: sender's nick is null");

	if (!irc_message_param(m, &chan))
		failf(s, "JOIN: target channel is null");
		failf(s, "JOIN: channel is null");

	if (!strcmp(m->from, s->nick)) {
		if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL) {


@@ 682,10 690,27 @@ recv_join(struct server *s, struct irc_message *m)
		failf(s, "JOIN: channel '%s' not found", chan);

	if (user_list_add(&(c->users), s->casemapping, m->from, MODE_EMPTY) == USER_ERR_DUPLICATE)
		failf(s, "JOIN: user '%s' alread on channel '%s'", m->from, chan);
		failf(s, "JOIN: user '%s' already on channel '%s'", m->from, chan);

	if (!join_threshold || join_threshold > c->users.count) {

		if (s->ircv3_caps.extended_join.set) {

	if (!join_threshold || c->users.count <= join_threshold)
		newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined", m->from, m->host);
			char *account;
			char *realname;

			if (!irc_message_param(m, &account))
				failf(s, "JOIN: account is null");

			if (!irc_message_param(m, &realname))
				failf(s, "JOIN: realname is null");

			newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined [%s - %s]",
				m->from, m->host, account, realname);
		} else {
			newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined", m->from, m->host);
		}
	}

	draw(DRAW_STATUS);



@@ 695,7 720,7 @@ recv_join(struct server *s, struct irc_message *m)
static int
recv_kick(struct server *s, struct irc_message *m)
{
	/* :nick!user@host KICK <channel> <user> [message] */
	/* :nick!user@host KICK <channel> <user> [:message] */

	char *chan;
	char *message;


@@ 727,7 752,7 @@ recv_kick(struct server *s, struct irc_message *m)

		channel_part(c);

		if (message)
		if (message && *message)
			newlinef(c, 0, FROM_INFO, "Kicked by %s (%s)", m->from, message);
		else
			newlinef(c, 0, FROM_INFO, "Kicked by %s", m->from);


@@ 737,7 762,7 @@ recv_kick(struct server *s, struct irc_message *m)
		if (user_list_del(&(c->users), s->casemapping, user) == USER_ERR_NOT_FOUND)
			failf(s, "KICK: nick '%s' not found in '%s'", user, chan);

		if (message)
		if (message && *message)
			newlinef(c, 0, FROM_INFO, "%s has kicked %s (%s)", m->from, user, message);
		else
			newlinef(c, 0, FROM_INFO, "%s has kicked %s", m->from, user);


@@ 794,8 819,8 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
	char flag;
	char *modestring;
	char *modearg;
	enum mode_err_t mode_err;
	enum mode_set_t mode_set;
	enum mode_err mode_err;
	enum mode_set mode_set;
	struct mode *chanmodes = &(c->chanmodes);
	struct user *user;



@@ 927,8 952,8 @@ recv_mode_usermodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
{
	char flag;
	char *modestring;
	enum mode_err_t mode_err;
	enum mode_set_t mode_set;
	enum mode_err mode_err;
	enum mode_set mode_set;
	struct mode *usermodes = &(s->usermodes);

	if (!irc_message_param(m, &modestring))


@@ 988,17 1013,17 @@ recv_nick(struct server *s, struct irc_message *m)

	if (!strcmp(m->from, s->nick)) {
		server_nick_set(s, nick);
		newlinef(s->channel, BUFFER_LINE_NICK, FROM_NICK, "Your nick is now '%s'", nick);
		newlinef(s->channel, BUFFER_LINE_NICK, FROM_INFO, "Your nick is now '%s'", nick);
	}

	do {
		enum user_err ret;

		if ((ret = user_list_rpl(&(c->users), s->casemapping, m->from, nick)) == USER_ERR_NONE)
			newlinef(c, BUFFER_LINE_NICK, FROM_NICK, "%s  >>  %s", m->from, nick);
			newlinef(c, BUFFER_LINE_NICK, FROM_INFO, "%s  >>  %s", m->from, nick);

		else if (ret == USER_ERR_DUPLICATE)
			server_error(s, "NICK: user '%s' alread on channel '%s'", m->from, c->name);
			server_error(s, "NICK: user '%s' already on channel '%s'", nick, c->name);

	} while ((c = c->next) != s->channel);



@@ 1008,7 1033,7 @@ recv_nick(struct server *s, struct irc_message *m)
static int
recv_notice(struct server *s, struct irc_message *m)
{
	/* :nick!user@host NOTICE <target> <message> */
	/* :nick!user@host NOTICE <target> :<message> */

	char *message;
	char *target;


@@ 1052,9 1077,9 @@ recv_notice(struct server *s, struct irc_message *m)
		if (c != current_channel())
			urgent = 1;

		newline(c, BUFFER_LINE_PINGED, m->from, message);
		newlinef(c, BUFFER_LINE_PINGED, m->from, "%s", message);
	} else {
		newline(c, BUFFER_LINE_CHAT, m->from, message);
		newlinef(c, BUFFER_LINE_CHAT, m->from, "%s", message);
	}

	if (urgent) {


@@ 1069,7 1094,7 @@ recv_notice(struct server *s, struct irc_message *m)
static int
recv_part(struct server *s, struct irc_message *m)
{
	/* :nick!user@host PART <channel> [message] */
	/* :nick!user@host PART <channel> [:message] */

	char *chan;
	char *message;


@@ 1079,14 1104,16 @@ recv_part(struct server *s, struct irc_message *m)
		failf(s, "PART: sender's nick is null");

	if (!irc_message_param(m, &chan))
		failf(s, "PART: target channel is null");
		failf(s, "PART: channel is null");

	irc_message_param(m, &message);

	if (!strcmp(m->from, s->nick)) {

		/* If not found, assume channel was closed */
		if ((c = channel_list_get(&s->clist, chan, s->casemapping)) != NULL) {

			if (irc_message_param(m, &message))
			if (message && *message)
				newlinef(c, BUFFER_LINE_PART, FROM_PART, "you have parted (%s)", message);
			else
				newlinef(c, BUFFER_LINE_PART, FROM_PART, "you have parted");


@@ 1101,8 1128,9 @@ recv_part(struct server *s, struct irc_message *m)
		if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NOT_FOUND)
			failf(s, "PART: nick '%s' not found in '%s'", m->from, chan);

		if (!part_threshold || c->users.count <= part_threshold) {
			if (irc_message_param(m, &message))
		if (!part_threshold || part_threshold > c->users.count) {

			if (message && *message)
				newlinef(c, 0, FROM_PART, "%s!%s has parted (%s)", m->from, m->host, message);
			else
				newlinef(c, 0, FROM_PART, "%s!%s has parted", m->from, m->host);


@@ 1132,7 1160,7 @@ recv_ping(struct server *s, struct irc_message *m)
static int
recv_pong(struct server *s, struct irc_message *m)
{
	/*  PONG <server> [<server2>] */
	/* PONG <server> [<server2>] */

	UNUSED(s);
	UNUSED(m);


@@ 1143,7 1171,7 @@ recv_pong(struct server *s, struct irc_message *m)
static int
recv_privmsg(struct server *s, struct irc_message *m)
{
	/* :nick!user@host PRIVMSG <target> <message> */
	/* :nick!user@host PRIVMSG <target> :<message> */

	char *message;
	char *target;


@@ 1185,9 1213,9 @@ recv_privmsg(struct server *s, struct irc_message *m)
		if (c != current_channel())
			urgent = 1;

		newline(c, BUFFER_LINE_PINGED, m->from, message);
		newlinef(c, BUFFER_LINE_PINGED, m->from, "%s", message);
	} else {
		newline(c, BUFFER_LINE_CHAT, m->from, message);
		newlinef(c, BUFFER_LINE_CHAT, m->from, "%s", message);
	}

	if (urgent) {


@@ 1200,9 1228,42 @@ recv_privmsg(struct server *s, struct irc_message *m)
}

static int
recv_quit(struct server *s, struct irc_message *m)
{
	/* :nick!user@host QUIT [:message] */

	char *message;
	struct channel *c = s->channel;

	if (!m->from)
		failf(s, "QUIT: sender's nick is null");

	irc_message_param(m, &message);

	do {
		if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NONE) {

			if (quit_threshold && quit_threshold <= c->users.count)
				continue;

			if (message && *message)
				newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit (%s)",
					m->from, m->host, message);
			else
				newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit",
					m->from, m->host);
		}
	} while ((c = c->next) != s->channel);

	draw(DRAW_STATUS);

	return 0;
}

static int
recv_topic(struct server *s, struct irc_message *m)
{
	/* :nick!user@host TOPIC <channel> [topic] */
	/* :nick!user@host TOPIC <channel> [:topic] */

	char *chan;
	char *topic;


@@ 1212,16 1273,16 @@ recv_topic(struct server *s, struct irc_message *m)
		failf(s, "TOPIC: sender's nick is null");

	if (!irc_message_param(m, &chan))
		failf(s, "TOPIC: target channel is null");
		failf(s, "TOPIC: channel is null");

	if (!irc_message_param(m, &topic))
		failf(s, "TOPIC: topic is null");

	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "TOPIC: target channel '%s' not found", chan);
		failf(s, "TOPIC: channel '%s' not found", chan);

	if (*topic) {
		newlinef(c, 0, FROM_INFO, "%s has changed the topic:", m->from);
		newlinef(c, 0, FROM_INFO, "%s has set the topic:", m->from);
		newlinef(c, 0, FROM_INFO, "\"%s\"", topic);
	} else {
		newlinef(c, 0, FROM_INFO, "%s has unset the topic", m->from);


@@ 1231,30 1292,100 @@ recv_topic(struct server *s, struct irc_message *m)
}

static int
recv_quit(struct server *s, struct irc_message *m)
recv_ircv3_cap(struct server *s, struct irc_message *m)
{
	/* :nick!user@host QUIT [message] */
	return ircv3_recv_CAP(s, m);
}

	char *message = NULL;
static int
recv_ircv3_account(struct server *s, struct irc_message *m)
{
	/* :nick!user@host ACCOUNT <account> */

	char *account;
	struct channel *c = s->channel;

	if (!m->from)
		failf(s, "QUIT: sender's nick is null");
		failf(s, "ACCOUNT: sender's nick is null");

	if (!irc_message_param(m, &account))
		failf(s, "ACCOUNT: account is null");

	do {
		if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
			continue;

		if (account_threshold && account_threshold <= c->users.count)
			continue;

		if (!strcmp(account, "*"))
			newlinef(c, 0, FROM_INFO, "%s has logged out", m->from);
		else
			newlinef(c, 0, FROM_INFO, "%s has logged in as %s", m->from, account);

	} while ((c = c->next) != s->channel);

	return 0;
}

static int
recv_ircv3_away(struct server *s, struct irc_message *m)
{
	/* :nick!user@host AWAY [:message] */

	char *message;
	struct channel *c = s->channel;

	if (!m->from)
		failf(s, "AWAY: sender's nick is null");

	irc_message_param(m, &message);

	do {
		if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NONE) {
			if (!quit_threshold || c->users.count <= quit_threshold) {
				if (message)
					newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit (%s)", m->from, m->host, message);
				else
					newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit", m->from, m->host);
			}
		}
		if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
			continue;

		if (away_threshold && away_threshold <= c->users.count)
			continue;

		if (message)
			newlinef(c, 0, FROM_INFO, "%s is now away: %s", m->from, message);
		else
			newlinef(c, 0, FROM_INFO, "%s is no longer away", m->from);

	} while ((c = c->next) != s->channel);

	draw(DRAW_STATUS);
	return 0;
}

static int
recv_ircv3_chghost(struct server *s, struct irc_message *m)
{
	/* :nick!user@host CHGHOST new_user new_host */

	char *user;
	char *host;
	struct channel *c = s->channel;

	if (!m->from)
		failf(s, "CHGHOST: sender's nick is null");

	if (!irc_message_param(m, &user))
		failf(s, "CHGHOST: user is null");

	if (!irc_message_param(m, &host))
		failf(s, "CHGHOST: host is null");

	do {
		if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
			continue;

		if (chghost_threshold && chghost_threshold <= c->users.count)
			continue;

		newlinef(c, 0, FROM_INFO, "%s has changed user/host: %s/%s", m->from, user, host);

	} while ((c = c->next) != s->channel);

	return 0;
}

M src/handlers/irc_recv.gperf => src/handlers/irc_recv.gperf +9 -3
@@ 2,7 2,6 @@
#include <string.h>

#define RECV_HANDLERS \
	X(cap) \
	X(error) \
	X(invite) \
	X(join) \


@@ 15,7 14,11 @@
	X(pong) \
	X(privmsg) \
	X(quit) \
	X(topic)
	X(topic) \
	X(ircv3_cap) \
	X(ircv3_account) \
	X(ircv3_away) \
	X(ircv3_chghost)

#define X(cmd) static int recv_##cmd(struct server*, struct irc_message*);
RECV_HANDLERS


@@ 41,7 44,6 @@ struct recv_handler
%define initializer-suffix ,(irc_recv_f)0
struct recv_handler;
%%
CAP,     recv_cap
ERROR,   recv_error
INVITE,  recv_invite
JOIN,    recv_join


@@ 55,4 57,8 @@ PONG,    recv_pong
PRIVMSG, recv_privmsg
QUIT,    recv_quit
TOPIC,   recv_topic
CAP,     recv_ircv3_cap
ACCOUNT, recv_ircv3_account
AWAY,    recv_ircv3_away
CHGHOST, recv_ircv3_chghost
%%

M src/handlers/irc_recv.h => src/handlers/irc_recv.h +2 -2
@@ 1,5 1,5 @@
#ifndef IRC_RECV_H
#define IRC_RECV_H
#ifndef RIRC_HANDLERS_IRC_RECV_H
#define RIRC_HANDLERS_IRC_RECV_H

/* Summary of irc protocol implementation:
 *

M src/handlers/irc_send.c => src/handlers/irc_send.c +82 -54
@@ 1,17 1,16 @@
#include <ctype.h>
#include <sys/time.h>
#include "src/handlers/irc_send.h"

#include "config.h"
#include "src/components/buffer.h"
#include "src/components/channel.h"
#include "src/components/ircv3.h"
#include "src/components/server.h"
#include "src/handlers/irc_send.gperf.out"
#include "src/handlers/irc_send.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"

#include <ctype.h>
#include <sys/time.h>

#define failf(C, ...) \
	do { newlinef((C), 0, FROM_ERROR, __VA_ARGS__); \
	     return 1; \


@@ 23,12 22,14 @@
	         failf((C), "Send fail: %s", io_err(ret)); \
	} while (0)

static const char* targ_or_type(struct channel*, char*, enum channel_t type);
static const char* nick_or_priv(struct channel*, char*);

int
irc_send_command(struct server *s, struct channel *c, char *m)
{
	char *command, *command_args, *p;
	char *command;
	char *command_args;
	char *p;
	const struct send_handler *send;

	if (!s)


@@ 37,18 38,18 @@ irc_send_command(struct server *s, struct channel *c, char *m)
	if (!s->registered)
		failf(c, "Not registered with server");

	if (*m == ' ' || !(command = strsep(&m)))
	if (*m == ' ' || !(command = irc_strsep(&m)))
		failf(c, "Messages beginning with '/' require a command");

	for (p = command; *p; p++)
		*p = toupper(*p);

	command_args = strtrim(&m);
	command_args = irc_strtrim(&m);

	if ((send = send_handler_lookup(command, strlen(command))))
		return send->f(s, c, command_args);

	if (strtrim(&command_args))
	if (irc_strtrim(&command_args))
		sendf(s, c, "%s %s", command, command_args);
	else
		sendf(s, c, "%s", command);


@@ 76,31 77,42 @@ irc_send_privmsg(struct server *s, struct channel *c, char *m)

	sendf(s, c, "PRIVMSG %s :%s", c->name, m);

	newline(c, BUFFER_LINE_CHAT, s->nick, m);
	newlinef(c, BUFFER_LINE_CHAT, s->nick, "%s", m);

	return 0;
}

static const char*
targ_or_type(struct channel *c, char *m, enum channel_t type)
nick_or_priv(struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if ((targ = strsep(&m)))
		return targ;
	if ((nick = irc_strsep(&m)))
		return nick;

	if (c->type == type)
	if (c->type == CHANNEL_T_PRIVATE)
		return c->name;

	return NULL;
}

static int
send_away(struct server *s, struct channel *c, char *m)
{
	if (irc_strtrim(&m))
		sendf(s, c, "AWAY :%s", m);
	else
		sendf(s, c, "AWAY");

	return 0;
}

static int
send_notice(struct server *s, struct channel *c, char *m)
{
	const char *targ;

	if (!(targ = strsep(&m)))
	if (!(targ = irc_strsep(&m)))
		failf(c, "Usage: /notice <target> <message>");

	if (!m || !*m)


@@ 117,7 129,7 @@ send_part(struct server *s, struct channel *c, char *m)
	if (c->type != CHANNEL_T_CHANNEL)
		failf(c, "This is not a channel");

	if (strtrim(&m))
	if (irc_strtrim(&m))
		sendf(s, c, "PART %s :%s", c->name, m);
	else
		sendf(s, c, "PART %s :%s", c->name, DEFAULT_PART_MESG);


@@ 130,7 142,7 @@ send_privmsg(struct server *s, struct channel *c, char *m)
{
	const char *targ;

	if (!(targ = strsep(&m)))
	if (!(targ = irc_strsep(&m)))
		failf(c, "Usage: /privmsg <target> <message>");

	if (!m || !*m)


@@ 146,7 158,7 @@ send_quit(struct server *s, struct channel *c, char *m)
{
	s->quitting = 1;

	if (strtrim(&m))
	if (irc_strtrim(&m))
		sendf(s, c, "QUIT :%s", m);
	else
		sendf(s, c, "QUIT :%s", DEFAULT_PART_MESG);


@@ 160,7 172,7 @@ send_topic(struct server *s, struct channel *c, char *m)
	if (c->type != CHANNEL_T_CHANNEL)
		failf(c, "This is not a channel");

	if (strtrim(&m))
	if (irc_strtrim(&m))
		sendf(s, c, "TOPIC %s :%s", c->name, m);
	else
		sendf(s, c, "TOPIC %s", c->name);


@@ 169,12 181,28 @@ send_topic(struct server *s, struct channel *c, char *m)
}

static int
send_ctcp_action(struct server *s, struct channel *c, char *m)
send_topic_unset(struct server *s, struct channel *c, char *m)
{
	if (!(c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE))
	if (c->type != CHANNEL_T_CHANNEL)
		failf(c, "This is not a channel");

	sendf(s, c, "PRIVMSG %s :\001ACTION %s\001", c->name, m);
	if (irc_strtrim(&m))
		failf(c, "Usage: /topic-unset");

	sendf(s, c, "TOPIC %s :", c->name);

	return 0;
}

static int
send_ctcp_action(struct server *s, struct channel *c, char *m)
{
	const char *nick;

	if (!(nick = irc_strsep(&m)) || !irc_strtrim(&m))
		failf(c, "Usage: /ctcp-action <nick> <text>");

	sendf(s, c, "PRIVMSG %s :\001ACTION %s\001", nick, m);

	return 0;
}


@@ 182,12 210,12 @@ send_ctcp_action(struct server *s, struct channel *c, char *m)
static int
send_ctcp_clientinfo(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-clientinfo <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-clientinfo <nick>");

	sendf(s, c, "PRIVMSG %s :\001CLIENTINFO\001", targ);
	sendf(s, c, "PRIVMSG %s :\001CLIENTINFO\001", nick);

	return 0;
}


@@ 195,12 223,12 @@ send_ctcp_clientinfo(struct server *s, struct channel *c, char *m)
static int
send_ctcp_finger(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-finger <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-finger <nick>");

	sendf(s, c, "PRIVMSG %s :\001FINGER\001", targ);
	sendf(s, c, "PRIVMSG %s :\001FINGER\001", nick);

	return 0;
}


@@ 208,15 236,15 @@ send_ctcp_finger(struct server *s, struct channel *c, char *m)
static int
send_ctcp_ping(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;
	struct timeval t;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-ping <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-ping <nick>");

	(void) gettimeofday(&t, NULL);

	sendf(s, c, "PRIVMSG %s :\001PING %llu %llu\001", targ,
	sendf(s, c, "PRIVMSG %s :\001PING %llu %llu\001", nick,
		(unsigned long long)t.tv_sec,
		(unsigned long long)t.tv_usec);



@@ 226,12 254,12 @@ send_ctcp_ping(struct server *s, struct channel *c, char *m)
static int
send_ctcp_source(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-source <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-source <nick>");

	sendf(s, c, "PRIVMSG %s :\001SOURCE\001", targ);
	sendf(s, c, "PRIVMSG %s :\001SOURCE\001", nick);

	return 0;
}


@@ 239,12 267,12 @@ send_ctcp_source(struct server *s, struct channel *c, char *m)
static int
send_ctcp_time(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-time <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-time <nick>");

	sendf(s, c, "PRIVMSG %s :\001TIME\001", targ);
	sendf(s, c, "PRIVMSG %s :\001TIME\001", nick);

	return 0;
}


@@ 252,12 280,12 @@ send_ctcp_time(struct server *s, struct channel *c, char *m)
static int
send_ctcp_userinfo(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-userinfo <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-userinfo <nick>");

	sendf(s, c, "PRIVMSG %s :\001USERINFO\001", targ);
	sendf(s, c, "PRIVMSG %s :\001USERINFO\001", nick);

	return 0;
}


@@ 265,12 293,12 @@ send_ctcp_userinfo(struct server *s, struct channel *c, char *m)
static int
send_ctcp_version(struct server *s, struct channel *c, char *m)
{
	const char *targ;
	const char *nick;

	if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
		failf(c, "Usage: /ctcp-version <target>");
	if (!(nick = nick_or_priv(c, m)))
		failf(c, "Usage: /ctcp-version <nick>");

	sendf(s, c, "PRIVMSG %s :\001VERSION\001", targ);
	sendf(s, c, "PRIVMSG %s :\001VERSION\001", nick);

	return 0;
}


@@ 278,7 306,7 @@ send_ctcp_version(struct server *s, struct channel *c, char *m)
static int
send_ircv3_cap_ls(struct server *s, struct channel *c, char *m)
{
	if (strtrim(&m))
	if (irc_strtrim(&m))
		failf(c, "Usage: /cap-ls");

	sendf(s, c, "CAP LS " IRCV3_CAP_VERSION);


@@ 289,7 317,7 @@ send_ircv3_cap_ls(struct server *s, struct channel *c, char *m)
static int
send_ircv3_cap_list(struct server *s, struct channel *c, char *m)
{
	if (strtrim(&m))
	if (irc_strtrim(&m))
		failf(c, "Usage: /cap-list");

	sendf(s, c, "CAP LIST");

M src/handlers/irc_send.gperf => src/handlers/irc_send.gperf +5 -1
@@ 2,11 2,13 @@
#include <string.h>

#define SEND_HANDLERS \
	X(away) \
	X(notice) \
	X(part) \
	X(privmsg) \
	X(quit) \
	X(topic)
	X(topic) \
	X(topic_unset)

#define SEND_CTCP_HANDLERS \
	X(action) \


@@ 64,9 66,11 @@ CTCP-SOURCE,     send_ctcp_source
CTCP-TIME,       send_ctcp_time
CTCP-USERINFO,   send_ctcp_userinfo
CTCP-VERSION,    send_ctcp_version
AWAY,            send_away
NOTICE,          send_notice
PART,            send_part
PRIVMSG,         send_privmsg
QUIT,            send_quit
TOPIC,           send_topic
TOPIC-UNSET,     send_topic_unset
%%

M src/handlers/irc_send.h => src/handlers/irc_send.h +2 -2
@@ 1,5 1,5 @@
#ifndef IRC_SEND_H
#define IRC_SEND_H
#ifndef RIRC_HANDLERS_IRC_SEND_H
#define RIRC_HANDLERS_IRC_SEND_H

#include "src/components/channel.h"
#include "src/components/server.h"

M src/handlers/ircv3.c => src/handlers/ircv3.c +62 -34
@@ 1,9 1,10 @@
#include <string.h>

#include "src/handlers/ircv3.h"

#include "src/io.h"
#include "src/state.h"

#include <string.h>

#define failf(S, ...) \
	do { server_error((S), __VA_ARGS__); \
	     return 1; \


@@ 29,6 30,7 @@ IRCV3_RECV_HANDLERS
#undef X

static int ircv3_cap_req_count(struct ircv3_caps*);
static int ircv3_cap_req_send(struct ircv3_caps*, struct server*);

int
ircv3_recv_CAP(struct server *s, struct irc_message *m)


@@ 92,27 94,26 @@ ircv3_recv_cap_LS(struct server *s, struct irc_message *m)
		return 0;
	}

	while ((cap = strsep(&(caps)))) {
	while ((cap = irc_strsep(&(caps)))) {

		struct ircv3_cap *c;

		if ((c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			c->supported = 1;
		if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap)))
			continue;

		c->supported = 1;

		if (c->req_auto)
			c->req = 1;
	}

	if (multiline)
		return 0;

	#define X(CAP, VAR, ATTRS)     \
	if (s->ircv3_caps.VAR.supported && s->ircv3_caps.VAR.req_auto) { \
		s->ircv3_caps.VAR.req = 1; \
		sendf(s, "CAP REQ :" CAP); \
	}
	IRCV3_CAPS
	#undef X
	if (ircv3_cap_req_count(&(s->ircv3_caps)))
		return ircv3_cap_req_send(&(s->ircv3_caps), s);

	if (!ircv3_cap_req_count(&(s->ircv3_caps)))
		sendf(s, "CAP END");
	sendf(s, "CAP END");

	return 0;
}


@@ 179,7 180,7 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &caps))
		failf(s, "CAP ACK: parameter is null");

	if (!(cap = strsep(&(caps))))
	if (!(cap = irc_strsep(&(caps))))
		failf(s, "CAP ACK: parameter is empty");

	do {


@@ 218,7 219,7 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)

		server_info(s, "capability change accepted: %s%s", (unset ? "-" : ""), cap);

	} while ((cap = strsep(&(caps))));
	} while ((cap = irc_strsep(&(caps))));

	if (errors)
		failf(s, "CAP ACK: parameter errors");


@@ 244,7 245,7 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &caps))
		failf(s, "CAP NAK: parameter is null");

	if (!(cap = strsep(&(caps))))
	if (!(cap = irc_strsep(&(caps))))
		failf(s, "CAP NAK: parameter is empty");

	do {


@@ 255,7 256,7 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)

		server_info(s, "capability change rejected: %s", cap);

	} while ((cap = strsep(&(caps))));
	} while ((cap = irc_strsep(&(caps))));

	if (!s->registered && !ircv3_cap_req_count(&(s->ircv3_caps)))
		sendf(s, "CAP END");


@@ 281,7 282,7 @@ ircv3_recv_cap_DEL(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &caps))
		failf(s, "CAP DEL: parameter is null");

	if (!(cap = strsep(&(caps))))
	if (!(cap = irc_strsep(&(caps))))
		failf(s, "CAP DEL: parameter is empty");

	do {


@@ 299,7 300,7 @@ ircv3_recv_cap_DEL(struct server *s, struct irc_message *m)

		server_info(s, "capability lost: %s", cap);

	} while ((cap = strsep(&(caps))));
	} while ((cap = irc_strsep(&(caps))));

	return 0;
}


@@ 316,29 317,34 @@ ircv3_recv_cap_NEW(struct server *s, struct irc_message *m)

	char *cap;
	char *caps;
	struct ircv3_caps batch_reqs = {0};

	if (!irc_message_param(m, &caps))
		failf(s, "CAP NEW: parameter is null");

	if (!(cap = strsep(&(caps))))
	if (!(cap = irc_strsep(&(caps))))
		failf(s, "CAP NEW: parameter is empty");

	do {
		struct ircv3_cap *c;
		struct ircv3_cap *c1 = ircv3_cap_get(&(s->ircv3_caps), cap);
		struct ircv3_cap *c2 = ircv3_cap_get(&batch_reqs, cap);

		if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap)))
		if (!c1 || !c2)
			continue;

		c->supported = 1;
		c1->supported = 1;

		if (!c->set && !c->req && c->req_auto) {
			c->req = 1;
			sendf(s, "CAP REQ :%s", cap);
		if (c1->set || c1->req || !c1->req_auto) {
			server_info(s, "new capability: %s", cap);
		} else {
			c1->req = 1;
			c2->req = 1;
			server_info(s, "new capability: %s (auto-req)", cap);
		}
	} while ((cap = irc_strsep(&(caps))));

		server_info(s, "new capability: %s", cap);

	} while ((cap = strsep(&(caps))));
	if (ircv3_cap_req_count(&batch_reqs))
		return ircv3_cap_req_send(&batch_reqs, s);

	return 0;
}


@@ 346,11 352,33 @@ ircv3_recv_cap_NEW(struct server *s, struct irc_message *m)
static int
ircv3_cap_req_count(struct ircv3_caps *caps)
{
	int ret = 0;
	#define X(CAP, VAR, ATTRS) + !!caps->VAR.req
	return 0 IRCV3_CAPS;
	#undef X
}

static int
ircv3_cap_req_send(struct ircv3_caps *caps, struct server *s)
{
	int mid = 0;
	int ret;

	#define X(CAP, VAR, ATTRS) \
	if (caps->VAR.req) \
		ret++;
	const char *sep_##VAR = (caps->VAR.req && mid++ ? " " : ""); \
	const char *str_##VAR = (caps->VAR.req          ? CAP : "");
	IRCV3_CAPS
	#undef X
	return ret;

	if ((ret = io_sendf(s->connection, "CAP REQ :"
		#define X(CAP, VAR, ATTRS) "%s%s"
		IRCV3_CAPS
		#undef X
		#define X(CAP, VAR, ATTRS) ,sep_##VAR,str_##VAR
		IRCV3_CAPS
		#undef X
	))) {
		failf(s, "Send fail: %s", io_err(ret));
	}

	return 0;
}

M src/handlers/ircv3.h => src/handlers/ircv3.h +2 -2
@@ 1,5 1,5 @@
#ifndef IRCV3_H
#define IRCV3_H
#ifndef RIRC_HANDLERS_IRCV3_H
#define RIRC_HANDLERS_IRCV3_H

#include "src/components/server.h"
#include "src/utils/utils.h"

M src/io.c => src/io.c +155 -182
@@ 1,8 1,20 @@
#include "src/io.h"

#include "config.h"
#include "src/rirc.h"
#include "src/utils/utils.h"

#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/x509_crt.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>


@@ 13,21 25,8 @@
#include <termios.h>
#include <unistd.h>

#include "config.h"
#include "rirc.h"
#include "utils/utils.h"

#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/x509_crt.h"

/* RFC 2812, section 2.3 */
#ifndef IO_MESG_LEN
#define IO_MESG_LEN 510
#endif

#ifndef IO_PING_MIN
#define IO_PING_MIN 150


@@ 72,28 71,24 @@
			io_fatal((#X), _ptcf); \
		}                          \
	} while (0)

#define PT_LK(X) PT_CF(pthread_mutex_lock((X)))
#define PT_UL(X) PT_CF(pthread_mutex_unlock((X)))
#define PT_CB(...) \
	do {                    \
		PT_LK(&io_cb_mutex);   \
		io_cb(__VA_ARGS__); \
		PT_UL(&io_cb_mutex);   \
	} while (0)

/* IO callback */
#define IO_CB(X) \
	do { PT_LK(&io_cb_mutex); (X); PT_UL(&io_cb_mutex); } while (0)

#define io_cxed(C)       IO_CB(io_cb_cxed((C)->obj))
#define io_dxed(C)       IO_CB(io_cb_dxed((C)->obj))
#define io_error(C, ...) IO_CB(io_cb_error((C)->obj,  __VA_ARGS__))
#define io_info(C, ...)  IO_CB(io_cb_info((C)->obj, __VA_ARGS__))
#define io_ping(C, P)    IO_CB(io_cb_ping((C)->obj, P))

/* state transition */
#define ST_X(OLD, NEW) (((OLD) << 3) | (NEW))

#define io_cb_cxed(C)        PT_CB(IO_CB_CXED, (C)->obj)
#define io_cb_dxed(C)        PT_CB(IO_CB_DXED, (C)->obj)
#define io_cb_err(C, ...)    PT_CB(IO_CB_ERR, (C)->obj, __VA_ARGS__)
#define io_cb_info(C, ...)   PT_CB(IO_CB_INFO, (C)->obj, __VA_ARGS__)
#define io_cb_ping_0(C, ...) PT_CB(IO_CB_PING_0, (C)->obj, __VA_ARGS__)
#define io_cb_ping_1(C, ...) PT_CB(IO_CB_PING_1, (C)->obj, __VA_ARGS__)
#define io_cb_ping_n(C, ...) PT_CB(IO_CB_PING_N, (C)->obj, __VA_ARGS__)
#define io_cb_signal(S)      PT_CB(IO_CB_SIGNAL, NULL, (S))

enum io_err_t
enum io_err
{
	IO_ERR_NONE,
	IO_ERR_CXED,


@@ 110,7 105,7 @@ struct connection
	const void *obj;
	const char *host;
	const char *port;
	enum io_state_t {
	enum io_state {
		IO_ST_INVALID,
		IO_ST_DXED, /* Socket disconnected, passive */
		IO_ST_RXNG, /* Socket disconnected, pending reconnect */


@@ 119,8 114,7 @@ struct connection
		IO_ST_PING, /* Socket connected, network state in question */
	} st_cur, /* current thread state */
	  st_new; /* new thread state */
	int soc;
	mbedtls_net_context tls_fd;
	mbedtls_net_context net_ctx;
	mbedtls_ssl_config  tls_conf;
	mbedtls_ssl_context tls_ctx;
	pthread_mutex_t mtx;


@@ 130,11 124,11 @@ struct connection
	unsigned rx_sleep;
};

static enum io_state_t io_state_cxed(struct connection*);
static enum io_state_t io_state_cxng(struct connection*);
static enum io_state_t io_state_ping(struct connection*);
static enum io_state_t io_state_rxng(struct connection*);
static int io_cx_read(struct connection*, unsigned);
static enum io_state io_state_cxed(struct connection*);
static enum io_state io_state_cxng(struct connection*);
static enum io_state io_state_ping(struct connection*);
static enum io_state io_state_rxng(struct connection*);
static int io_cx_read(struct connection*, uint32_t);
static void io_fatal(const char*, int);
static void io_sig_handle(int);
static void io_sig_init(void);


@@ 149,10 143,7 @@ static mbedtls_entropy_context  tls_entropy;
static mbedtls_x509_crt         tls_x509_crt;
static pthread_mutex_t io_cb_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct termios term;
static unsigned io_cols;
static unsigned io_rows;
static volatile sig_atomic_t flag_sigwinch_cb; /* sigwinch callback */
static volatile sig_atomic_t flag_tty_resized; /* sigwinch ws resize */

static const char* io_strerror(char*, size_t);
static int io_net_connect(struct connection*);


@@ 196,14 187,13 @@ connection_free(struct connection *cx)
int
io_cx(struct connection *cx)
{
	enum io_err_t err = IO_ERR_NONE;
	enum io_state_t st;
	enum io_err err = IO_ERR_NONE;
	sigset_t sigset;
	sigset_t sigset_old;

	PT_LK(&(cx->mtx));

	switch ((st = cx->st_cur)) {
	switch (cx->st_cur) {
		case IO_ST_DXED:
			PT_CF(sigfillset(&sigset));
			PT_CF(pthread_sigmask(SIG_BLOCK, &sigset, &sigset_old));


@@ 233,7 223,7 @@ io_cx(struct connection *cx)
int
io_dx(struct connection *cx)
{
	enum io_err_t err = IO_ERR_NONE;
	enum io_err err = IO_ERR_NONE;

	if (cx->st_cur == IO_ST_DXED)
		return IO_ERR_DXED;


@@ 280,34 270,27 @@ io_sendf(struct connection *cx, const char *fmt, ...)
	ret = 0;
	written = 0;

	if (cx->flags & IO_TLS_ENABLED) {
		do {
			if ((ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret)) < 0) {
				switch (ret) {
					case MBEDTLS_ERR_SSL_WANT_READ:
					case MBEDTLS_ERR_SSL_WANT_WRITE:
						ret = 0;
						continue;
					default:
						io_dx(cx);
						io_cx(cx);
						return IO_ERR_SSL_WRITE;
				}
			}
		} while ((written += ret) < len);
	} else {
		do {
			if ((ret = send(cx->soc, sendbuf + ret, len - ret, 0)) == -1) {
				switch (errno) {
					case EINTR:
						ret = 0;
						continue;
					default:
						return IO_ERR_SSL_WRITE;
				}
			}
		} while ((written += ret) < len);
	}
	do {
		if (cx->flags & IO_TLS_ENABLED) {
			ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret);
		} else {
			ret = mbedtls_net_send(&(cx->net_ctx), sendbuf + ret, len - ret);
		}

		if (ret >= 0)
			continue;

		switch (ret) {
			case MBEDTLS_ERR_SSL_WANT_READ:
			case MBEDTLS_ERR_SSL_WANT_WRITE:
				ret = 0;
				continue;
			default:
				io_dx(cx);
				io_cx(cx);
				return IO_ERR_SSL_WRITE;
		}
	} while ((written += ret) < len);

	return IO_ERR_NONE;
}


@@ 331,14 314,12 @@ io_start(void)
		ssize_t ret = read(STDIN_FILENO, buf, sizeof(buf));

		if (ret > 0) {
			PT_LK(&io_cb_mutex);
			io_cb_read_inp(buf, ret);
			PT_UL(&io_cb_mutex);
			IO_CB(io_cb_read_inp(buf, ret));
		} else {
			if (errno == EINTR) {
				if (flag_sigwinch_cb) {
					flag_sigwinch_cb = 0;
					io_cb_signal(IO_SIGWINCH);
					io_tty_winsize();
				}
			} else {
				fatal("read: %s", ret ? strerror(errno) : "EOF");


@@ 356,31 337,12 @@ io_stop(void)
static void
io_tty_winsize(void)
{
	static struct winsize tty_ws;

	if (flag_tty_resized == 0) {
		flag_tty_resized = 1;

		if (ioctl(0, TIOCGWINSZ, &tty_ws) < 0)
			fatal("ioctl: %s", strerror(errno));
	struct winsize tty_ws;

		io_rows = tty_ws.ws_row;
		io_cols = tty_ws.ws_col;
	}
}
	if (ioctl(0, TIOCGWINSZ, &tty_ws) < 0)
		fatal("ioctl: %s", strerror(errno));

unsigned
io_tty_cols(void)
{
	io_tty_winsize();
	return io_cols;
}

unsigned
io_tty_rows(void)
{
	io_tty_winsize();
	return io_rows;
	IO_CB(io_cb_sigwinch(tty_ws.ws_col, tty_ws.ws_row));
}

const char*


@@ 400,7 362,7 @@ io_err(int err)
	}
}

static enum io_state_t
static enum io_state
io_state_rxng(struct connection *cx)
{
	if (cx->rx_sleep == 0) {


@@ 412,7 374,7 @@ io_state_rxng(struct connection *cx)
		);
	}

	io_cb_info(cx, "Attemping reconnect in %02u:%02u",
	io_info(cx, "Attemping reconnect in %02u:%02u",
		(cx->rx_sleep / 60),
		(cx->rx_sleep % 60));



@@ 421,10 383,10 @@ io_state_rxng(struct connection *cx)
	return IO_ST_CXNG;
}

static enum io_state_t
static enum io_state
io_state_cxng(struct connection *cx)
{
	if ((cx->soc = io_net_connect(cx)) < 0)
	if ((io_net_connect(cx)) < 0)
		return IO_ST_RXNG;

	if ((cx->flags & IO_TLS_ENABLED) && io_tls_establish(cx) < 0)


@@ 433,12 395,12 @@ io_state_cxng(struct connection *cx)
	return IO_ST_CXED;
}

static enum io_state_t
static enum io_state
io_state_cxed(struct connection *cx)
{
	int ret;

	while ((ret = io_cx_read(cx, IO_PING_MIN)) > 0)
	while ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_MIN))) > 0)
		continue;

	if (ret == MBEDTLS_ERR_SSL_TIMEOUT)


@@ 446,28 408,30 @@ io_state_cxed(struct connection *cx)

	switch (ret) {
		case MBEDTLS_ERR_SSL_WANT_READ:
		case MBEDTLS_ERR_SSL_WANT_WRITE:
			break;
		case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
			io_cb_info(cx, "connection closed gracefully");
			io_info(cx, "connection closed gracefully");
			break;
		case MBEDTLS_ERR_NET_CONN_RESET:
		case 0:
			io_cb_err(cx, "connection reset by peer");
			io_error(cx, "connection reset by peer");
			break;
		default:
			io_cb_err(cx, "connection tls error");
			io_error(cx, "connection error");
			break;
	}

	mbedtls_net_free(&(cx->tls_fd));
	mbedtls_ssl_config_free(&(cx->tls_conf));
	mbedtls_ssl_free(&(cx->tls_ctx));
	mbedtls_net_free(&(cx->net_ctx));

	if (cx->flags & IO_TLS_ENABLED) {
		mbedtls_ssl_config_free(&(cx->tls_conf));
		mbedtls_ssl_free(&(cx->tls_ctx));
	}

	return IO_ST_CXNG;
}

static enum io_state_t
static enum io_state
io_state_ping(struct connection *cx)
{
	int ret;


@@ 475,7 439,7 @@ io_state_ping(struct connection *cx)
	if (cx->ping >= IO_PING_MAX)
		return IO_ST_CXNG;

	if ((ret = io_cx_read(cx, IO_PING_REFRESH)) > 0)
	if ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_REFRESH))) > 0)
		return IO_ST_CXED;

	if (ret == MBEDTLS_ERR_SSL_TIMEOUT)


@@ 483,23 447,25 @@ io_state_ping(struct connection *cx)

	switch (ret) {
		case MBEDTLS_ERR_SSL_WANT_READ:
		case MBEDTLS_ERR_SSL_WANT_WRITE:
			break;
		case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
			io_cb_info(cx, "connection closed gracefully");
			io_info(cx, "connection closed gracefully");
			break;
		case MBEDTLS_ERR_NET_CONN_RESET:
		case 0:
			io_cb_err(cx, "connection reset by peer");
			io_error(cx, "connection reset by peer");
			break;
		default:
			io_cb_err(cx, "connection ssl error");
			io_error(cx, "connection error");
			break;
	}

	mbedtls_net_free(&(cx->tls_fd));
	mbedtls_ssl_config_free(&(cx->tls_conf));
	mbedtls_ssl_free(&(cx->tls_ctx));
	mbedtls_net_free(&(cx->net_ctx));

	if (cx->flags & IO_TLS_ENABLED) {
		mbedtls_ssl_config_free(&(cx->tls_conf));
		mbedtls_ssl_free(&(cx->tls_ctx));
	}

	return IO_ST_CXNG;
}


@@ 519,9 485,11 @@ io_thread(void *arg)

	cx->st_cur = IO_ST_CXNG;

	io_info(cx, "Connecting to %s:%s", cx->host, cx->port);

	do {
		enum io_state_t st_cur;
		enum io_state_t st_new;
		enum io_state st_cur;
		enum io_state st_new;

		switch ((st_cur = cx->st_cur)) {
			case IO_ST_CXED: st_new = io_state_cxed(cx); break;


@@ 547,43 515,40 @@ io_thread(void *arg)
		switch (ST_X(st_cur, st_new)) {
			case ST_X(IO_ST_DXED, IO_ST_CXNG): /* A1 */
			case ST_X(IO_ST_RXNG, IO_ST_CXNG): /* A2,C */
				io_cb_info(cx, "Connecting to %s:%s", cx->host, cx->port);
				io_info(cx, "Connecting to %s:%s", cx->host, cx->port);
				break;
			case ST_X(IO_ST_CXED, IO_ST_CXNG): /* F1 */
				io_cb_dxed(cx);
				io_dxed(cx);
				break;
			case ST_X(IO_ST_PING, IO_ST_CXNG): /* F2 */
				io_cb_err(cx, "Connection timeout (%u)", cx->ping);
				io_cb_dxed(cx);
				io_error(cx, "Connection timeout (%u)", cx->ping);
				io_dxed(cx);
				break;
			case ST_X(IO_ST_RXNG, IO_ST_DXED): /* B1 */
			case ST_X(IO_ST_CXNG, IO_ST_DXED): /* B2 */
				io_cb_info(cx, "Connection cancelled");
				io_info(cx, "Connection cancelled");
				break;
			case ST_X(IO_ST_CXED, IO_ST_DXED): /* B3 */
			case ST_X(IO_ST_PING, IO_ST_DXED): /* B4 */
				io_cb_info(cx, "Connection closed");
				io_cb_dxed(cx);
				io_info(cx, "Connection closed");
				io_dxed(cx);
				break;
			case ST_X(IO_ST_CXNG, IO_ST_CXED): /* D */
				io_cb_info(cx, " .. Connection successful");
				io_cb_cxed(cx);
				io_info(cx, " .. Connection successful");
				io_cxed(cx);
				cx->rx_sleep = 0;
				break;
			case ST_X(IO_ST_CXNG, IO_ST_RXNG): /* E */
				io_cb_err(cx, " .. Connection failed -- retrying");
				io_error(cx, " .. Connection failed -- retrying");
				break;
			case ST_X(IO_ST_CXED, IO_ST_PING): /* G */
				cx->ping = IO_PING_MIN;
				io_cb_ping_1(cx, cx->ping);
				io_ping(cx, (cx->ping = IO_PING_MIN));
				break;
			case ST_X(IO_ST_PING, IO_ST_PING): /* H */
				cx->ping += IO_PING_REFRESH;
				io_cb_ping_n(cx, cx->ping);
				io_ping(cx, (cx->ping += IO_PING_REFRESH));
				break;
			case ST_X(IO_ST_PING, IO_ST_CXED): /* I */
				cx->ping = 0;
				io_cb_ping_0(cx, cx->ping);
				io_ping(cx, (cx->ping = 0));
				break;
			default:
				fatal("BAD ST_X from: %d to: %d", st_cur, st_new);


@@ 595,24 560,35 @@ io_thread(void *arg)
}

static int
io_cx_read(struct connection *cx, unsigned timeout)
io_cx_read(struct connection *cx, uint32_t timeout)
{
	int ret;
	struct pollfd fd[1];
	unsigned char buf[1024];

	fd[0].fd = cx->net_ctx.fd;
	fd[0].events = POLLIN;

	while ((ret = poll(fd, 1, timeout)) < 0 && errno == EAGAIN)
		continue;

	if (ret == 0)
		return MBEDTLS_ERR_SSL_TIMEOUT;

	if (ret < 0 && errno == EINTR)
		return MBEDTLS_ERR_SSL_WANT_READ;

	if (ret < 0)
		fatal("poll: %s", strerror(errno));

	if (cx->flags & IO_TLS_ENABLED) {
		mbedtls_ssl_conf_read_timeout(&(cx->tls_conf), SEC_IN_MS(timeout));
		if ((ret = mbedtls_ssl_read(&(cx->tls_ctx), buf, sizeof(buf))) > 0) {
			PT_LK(&io_cb_mutex);
			io_cb_read_soc((char *)buf, (size_t)ret,  cx->obj);
			PT_UL(&io_cb_mutex);
		}
		ret = mbedtls_ssl_read(&(cx->tls_ctx), buf, sizeof(buf));
	} else {
		while ((ret = recv(cx->soc, buf, sizeof(buf), 0)) > 0) {
			PT_LK(&io_cb_mutex);
			io_cb_read_soc((char *)buf, (size_t)ret,  cx->obj);
			PT_UL(&io_cb_mutex);
		}
		ret = mbedtls_net_recv(&(cx->net_ctx), buf, sizeof(buf));
	}

	if (ret > 0) {
		IO_CB(io_cb_read_soc((char *)buf, (size_t)ret,  cx->obj));
	}

	return ret;


@@ 633,10 609,8 @@ io_fatal(const char *f, int errnum)
static void
io_sig_handle(int sig)
{
	if (sig == SIGWINCH) {
	if (sig == SIGWINCH)
		flag_sigwinch_cb = 1;
		flag_tty_resized = 0;
	}
}

static void


@@ 676,6 650,8 @@ io_tty_init(void)

	if (atexit(io_tty_term))
		fatal("atexit");

	io_tty_winsize();
}

static void


@@ 716,10 692,10 @@ io_net_connect(struct connection *cx)
			return -1;

		if (ret == EAI_SYSTEM) {
			io_cb_err(cx, " .. Failed to resolve host: %s",
			io_error(cx, " .. Failed to resolve host: %s",
				io_strerror(buf, sizeof(buf)));
		} else {
			io_cb_err(cx, " .. Failed to resolve host: %s",
			io_error(cx, " .. Failed to resolve host: %s",
				gai_strerror(ret));
		}



@@ 742,13 718,13 @@ io_net_connect(struct connection *cx)
			goto err;
	}

	if (!p && soc < -1) {
		io_cb_err(cx, " .. Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
	if (!p && soc < 0) {
		io_error(cx, " .. Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
		goto err;
	}

	if (!p && soc >= 0) {
		io_cb_err(cx, " .. Failed to connect: %s", io_strerror(buf, sizeof(buf)));
		io_error(cx, " .. Failed to connect: %s", io_strerror(buf, sizeof(buf)));
		goto err;
	}



@@ 758,14 734,14 @@ io_net_connect(struct connection *cx)
		addr = &(((struct sockaddr_in6*)p->ai_addr)->sin6_addr);

	if (inet_ntop(p->ai_family, addr, buf, sizeof(buf)))
		io_cb_info(cx, " .. Connected [%s]", buf);
		io_info(cx, " .. Connected [%s]", buf);

	ret = soc;

err:
	freeaddrinfo(res);

	return ret;
	return (cx->net_ctx.fd = ret);
}

static void


@@ 793,20 769,17 @@ io_tls_establish(struct connection *cx)
{
	int ret;

	io_cb_info(cx, " .. Establishing TLS connection");
	io_info(cx, " .. Establishing TLS connection");

	mbedtls_net_init(&(cx->tls_fd));
	mbedtls_ssl_init(&(cx->tls_ctx));
	mbedtls_ssl_config_init(&(cx->tls_conf));

	cx->tls_fd.fd = cx->soc;

	if ((ret = mbedtls_ssl_config_defaults(
			&(cx->tls_conf),
			MBEDTLS_SSL_IS_CLIENT,
			MBEDTLS_SSL_TRANSPORT_STREAM,
			MBEDTLS_SSL_PRESET_DEFAULT))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}



@@ 834,27 807,27 @@ io_tls_establish(struct connection *cx)
			mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_REQUIRED);
	}

	if ((ret = mbedtls_net_set_block(&(cx->tls_fd)))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
	if ((ret = mbedtls_net_set_block(&(cx->net_ctx)))) {
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if ((ret = mbedtls_ssl_setup(&(cx->tls_ctx), &(cx->tls_conf)))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if ((ret = mbedtls_ssl_set_hostname(&(cx->tls_ctx), cx->host))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	mbedtls_ssl_set_bio(
		&(cx->tls_ctx),
		&(cx->tls_fd),
		&(cx->net_ctx),
		mbedtls_net_send,
		NULL,
		mbedtls_net_recv_timeout);
		mbedtls_net_recv,
		NULL);

	while ((ret = mbedtls_ssl_handshake(&(cx->tls_ctx)))) {
		if (ret != MBEDTLS_ERR_SSL_WANT_READ


@@ 863,31 836,31 @@ io_tls_establish(struct connection *cx)
	}

	if (ret && cx->flags & IO_TLS_VRFY_DISABLED) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if (io_tls_x509_vrfy(cx) < 0)
		io_cb_err(cx, " .... Unknown x509 error");
		io_error(cx, " .... Unknown x509 error");

	if (ret) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		io_error(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	io_cb_info(cx, " .. TLS connection established");
	io_cb_info(cx, " ..   - version:     %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
	io_cb_info(cx, " ..   - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));
	io_info(cx, " .. TLS connection established");
	io_info(cx, " ..   - version:     %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
	io_info(cx, " ..   - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));

	return 0;

err:

	io_cb_err(cx, " .. TLS connection failure");
	io_error(cx, " .. TLS connection failure");

	mbedtls_ssl_config_free(&(cx->tls_conf));
	mbedtls_ssl_free(&(cx->tls_ctx));
	mbedtls_net_free(&(cx->tls_fd));
	mbedtls_net_free(&(cx->net_ctx));

	return -1;
}


@@ 914,7 887,7 @@ io_tls_x509_vrfy(struct connection *cx)
		if ((p = strchr(buf, '\n')))
			*p++ = 0;

		io_cb_err(cx, " .... %s", s);
		io_error(cx, " .... %s", s);

	} while ((s = p));


M src/io.h => src/io.h +23 -57
@@ 1,5 1,5 @@
#ifndef IO_H
#define IO_H
#ifndef RIRC_IO_H
#define RIRC_IO_H

/* Handling off all network io, user input and signals
 *


@@ 49,20 49,17 @@
 *   (B) io_dx: close network connection
 *
 * Network state implicit transitions result in informational callback types:
 *   (C) on connection attempt:  IO_CB_INFO
 *   (E) on connection failure:  IO_CB_ERROR
 *   (D) on connection success:  IO_CB_CXED
 *   (F) on connection loss:     IO_CB_DXED
 *   (G) on ping timeout start:  IO_CB_PING_1
 *   (H) on ping timeout update: IO_CB_PING_N
 *   (I) on ping normal:         IO_CB_PING_0
 *   (D) on connection success:  io_cb_cxed
 *   (F) on connection loss:     io_cb_dxed
 *   (G) on ping timeout start:  io_cb_ping
 *   (H) on ping timeout update: io_cb_ping
 *   (I) on ping normal:         io_cb_ping
 *
 * Successful reads on stdin and connected sockets result in data callbacks:
 *   from stdin:  io_cb_read_inp
 *   from socket: io_cb_read_soc
 *
 * Signals registered to be caught result in non-signal handler context
 * callback with type IO_CB_SIGNAL
 * SIGWINCH results in a non signal-handler context callback io_cb_singwinch
 *
 * Failed connection attempts enter a retry cycle with exponential
 * backoff time given by:


@@ 77,37 74,6 @@
#include <stddef.h>
#include <stdint.h>

struct connection;

enum io_cb_t
{
	IO_CB_INVALID,
	IO_CB_CXED,   /* no args */
	IO_CB_DXED,   /* no args */
	IO_CB_ERR,    /* <const char *fmt>, [args, ...] */
	IO_CB_INFO,   /* <const char *fmt>, [args, ...] */
	IO_CB_PING_0, /* <unsigned ping> */
	IO_CB_PING_1, /* <unsigned ping> */
	IO_CB_PING_N, /* <unsigned ping> */
	IO_CB_SIGNAL, /* <io_sig_t sig> */
	IO_CB_SIZE
};

enum io_log_level
{
	IO_LOG_ERROR,
	IO_LOG_WARN,
	IO_LOG_INFO,
	IO_LOG_DEBUG,
};

enum io_sig_t
{
	IO_SIG_INVALID,
	IO_SIGWINCH,
	IO_SIG_SIZE
};

#define IO_IPV_UNSPEC        (1 << 1)
#define IO_IPV_4             (1 << 2)
#define IO_IPV_6             (1 << 3)


@@ 117,7 83,8 @@ enum io_sig_t
#define IO_TLS_VRFY_OPTIONAL (1 << 7)
#define IO_TLS_VRFY_REQUIRED (1 << 8)

/* Returns a connection, or NULL if limit is reached */
struct connection;

struct connection* connection(
	const void*, /* callback object */
	const char*, /* host */


@@ 133,26 100,25 @@ int io_dx(struct connection*);
/* Formatted write to connection */
int io_sendf(struct connection*, const char*, ...);

/* Init/start/stop IO context */
void io_init(void);
void io_start(void);
void io_stop(void);

/* Get tty dimensions */
unsigned io_tty_cols(void);
unsigned io_tty_rows(void);

/* IO error string */
const char* io_err(int);

/* IO state callback */
void io_cb(enum io_cb_t, const void*, ...);

/* IO data callback */
void io_cb_read_inp(char*, size_t);
void io_cb_read_soc(char*, size_t, const void*);

/* Log message callback */
void io_cb_log(const void*, enum io_log_level, const char*, ...);
/* IO event callbacks */
void io_cb_cxed(const void*);
void io_cb_dxed(const void*);
void io_cb_ping(const void*, unsigned);
void io_cb_sigwinch(unsigned, unsigned);

/* IO informational callbacks */
void io_cb_error(const void*, const char*, ...);
void io_cb_info(const void*, const char*, ...);

void io_init(void);
void io_start(void);
void io_stop(void);

#endif

M src/rirc.c => src/rirc.c +8 -7
@@ 1,3 1,10 @@
#include "src/rirc.h"

#include "config.h"
#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"

#include <errno.h>
#include <getopt.h>
#include <pwd.h>


@@ 6,11 13,6 @@
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"

#define MAX_CLI_SERVERS 16

#define arg_error(...) \


@@ 287,7 289,6 @@ parse_args(int argc, char **argv)
		default_realname = getpwuid_pw_name();

	state_init();
	draw_init();

	for (size_t i = 0; i < n_servers; i++) {



@@ 344,8 345,8 @@ main(int argc, char **argv)

	if ((ret = parse_args(argc, argv)) == 0) {
		io_start();
		draw_term();
		state_term();
		draw(DRAW_CLEAR);
	}

	return ret;

M src/rirc.h => src/rirc.h +2 -2
@@ 1,5 1,5 @@
#ifndef RIRC_H
#define RIRC_H
#ifndef RIRC_RIRC_H
#define RIRC_RIRC_H

/* Default config values obtained at runtime */


M src/state.c => src/state.c +310 -423
@@ 1,32 1,25 @@
/**
 * state.c
 *
 * All manipulation of global program state
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <stdio.h>
#include "src/state.h"

#include "config.h"
#include "src/components/channel.h"
#include "src/draw.h"
#include "src/handlers/irc_recv.h"
#include "src/handlers/irc_send.h"
#include "src/io.h"
#include "src/rirc.h"
#include "src/state.h"
#include "src/utils/utils.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <stdio.h>

/* See: https://vt100.net/docs/vt100-ug/chapter3.html */
#define CTRL(k) ((k) & 0x1f)

static void _newline(struct channel*, enum buffer_line_t, const char*, const char*, va_list);
static void state_io_cxed(struct server*);
static void state_io_dxed(struct server*);
static void state_io_ping(struct server*, unsigned int);
static void state_io_signal(enum io_sig_t);
static void _newline(struct channel*, enum buffer_line_type, const char*, const char*, va_list);

static int state_input_linef(struct channel*);
static int state_input_ctrlch(const char*, size_t);


@@ 36,6 29,18 @@ static uint16_t state_complete(char*, uint16_t, uint16_t, int);
static uint16_t state_complete_list(char*, uint16_t, uint16_t, const char**);
static uint16_t state_complete_user(char*, uint16_t, uint16_t, int);

static void state_channel_clear(int);
static void state_channel_close(int);

static void channel_move_prev(void);
static void channel_move_next(void);

static int action_clear(char);
static int action_close(char);
static int action_error(char);
static int (*action_handler)(char);
static char action_buff[256];

static void command(struct channel*, char*);

static struct


@@ 45,6 50,9 @@ static struct
	struct server_list servers;
} state;

static unsigned state_tty_cols;
static unsigned state_tty_rows;

struct server_list*
state_server_list(void)
{


@@ 69,6 77,8 @@ static const char *irc_list[] = {
	"ctcp-time",
	"ctcp-userinfo",
	"ctcp-version",
	"away",
	"topic-unset",
	"admin",   "connect", "info",     "invite", "join",
	"kick",    "kill",    "links",    "list",   "lusers",
	"mode",    "motd",    "names",    "nick",   "notice",


@@ 77,38 87,42 @@ static const char *irc_list[] = {
	"time",    "topic",   "trace",    "user",   "version",
	"who",     "whois",   "whowas",   NULL };

// TODO: from command handler list
/* List of rirc commands for tab completeion */
static const char *cmd_list[] = {
	"clear", "close", "connect", "disconnect", "quit", "set", NULL};
	"clear", "close", "connect", "disconnect", "quit", NULL};

void
state_init(void)
{
	state.default_channel = state.current_channel = channel("rirc", CHANNEL_T_OTHER);

	newline(state.default_channel, 0, "--", "      _");
	newline(state.default_channel, 0, "--", " _ __(_)_ __ ___");
	newline(state.default_channel, 0, "--", "| '__| | '__/ __|");
	newline(state.default_channel, 0, "--", "| |  | | | | (__");
	newline(state.default_channel, 0, "--", "|_|  |_|_|  \\___|");
	newline(state.default_channel, 0, "--", "");
	newline(state.default_channel, 0, "--", " - version " VERSION);
	newline(state.default_channel, 0, "--", " - compiled " __DATE__ ", " __TIME__);
	state.default_channel = state.current_channel = channel("rirc", CHANNEL_T_RIRC);

	newlinef(state.default_channel, 0, FROM_INFO, "      _");
	newlinef(state.default_channel, 0, FROM_INFO, " _ __(_)_ __ ___");
	newlinef(state.default_channel, 0, FROM_INFO, "| '__| | '__/ __|");
	newlinef(state.default_channel, 0, FROM_INFO, "| |  | | | | (__");
	newlinef(state.default_channel, 0, FROM_INFO, "|_|  |_|_|  \\___|");
	newlinef(state.default_channel, 0, FROM_INFO, "");
	newlinef(state.default_channel, 0, FROM_INFO, " - version %s", VERSION);
	newlinef(state.default_channel, 0, FROM_INFO, " - compiled %s, %s", __DATE__, __TIME__);
#ifndef NDEBUG
	newline(state.default_channel, 0, "--", " - compiled with DEBUG flags");
	newlinef(state.default_channel, 0, FROM_INFO, " - compiled with DEBUG flags");
#endif
}

void
state_term(void)
{
	/* Exit handler; must return normally */

	struct server *s1, *s2;
	struct server *s1;
	struct server *s2;

	channel_free(state.default_channel);

	state.current_channel = NULL;
	state.default_channel = NULL;

	action_handler = NULL;
	action_buff[0] = 0;

	if ((s1 = state_server_list()->head) == NULL)
		return;



@@ 118,18 132,25 @@ state_term(void)
		connection_free(s2->connection);
		server_free(s2);
	} while (s1 != state_server_list()->head);

	state.servers.head = NULL;
	state.servers.tail = NULL;
}

void
newline(struct channel *c, enum buffer_line_t type, const char *from, const char *mesg)
unsigned
state_cols(void)
{
	/* Default wrapper for _newline */
	return state_tty_cols;
}

	newlinef(c, type, from, "%s", mesg);
unsigned
state_rows(void)
{
	return state_tty_rows;
}

void
newlinef(struct channel *c, enum buffer_line_t type, const char *from, const char *fmt, ...)
newlinef(struct channel *c, enum buffer_line_type type, const char *from, const char *fmt, ...)
{
	/* Formating wrapper for _newline */



@@ 141,7 162,7 @@ newlinef(struct channel *c, enum buffer_line_t type, const char *from, const cha
}

static void
_newline(struct channel *c, enum buffer_line_t type, const char *from, const char *fmt, va_list ap)
_newline(struct channel *c, enum buffer_line_type type, const char *from, const char *fmt, va_list ap)
{
	char buf[TEXT_LENGTH_MAX];
	char prefix = 0;


@@ 154,7 175,7 @@ _newline(struct channel *c, enum buffer_line_t type, const char *from, const cha
	if ((len = vsnprintf(buf, sizeof(buf), fmt, ap)) < 0) {
		text_str = "newlinef error: vsprintf failure";
		text_len = strlen(text_str);
		from_str = "-!!-";
		from_str = FROM_ERROR;
		from_len = strlen(from_str);
	} else {
		text_str = buf;


@@ 230,106 251,56 @@ state_server_set_chans(struct server *s, const char *chans)
	return 0;
}

void
channel_clear(struct channel *c)
{
	memset(&(c->buffer), 0, sizeof(c->buffer));
	draw(DRAW_BUFFER);
}

/* WIP:
 *
 * removed action subsystem from input.c
 *
 * eventually should go in action.{h,c}
 *
 */
/* Max length of user action message */
#define MAX_ACTION_MESG 256
char *action_message;
static int action_close_server(char);
/* Action handling */
static int (*action_handler)(char);
static char action_buff[MAX_ACTION_MESG];
/* Incremental channel search */
static int action_find_channel(char);
/* TODO: This is a first draft for simple channel searching functionality.
 *
 * It can be cleaned up, and input.c is probably not the most ideal place for this */
#define MAX_SEARCH 128
struct channel *search_cptr; /* Used for iterative searching, before setting the current channel */
static char search_buf[MAX_SEARCH];
static size_t search_i;

static struct channel* search_channels(struct channel*, char*);
static struct channel*
search_channels(struct channel *start, char *search)
{
	if (start == NULL || *search == '\0')
		return NULL;

	/* Start the search one past the input */
	struct channel *c = channel_get_next(start);

	while (c != start) {

		if (strstr(c->name, search))
			return c;

		c = channel_get_next(c);
	}

	return NULL;
}
static int
state_input_action(const char *input, size_t len)
{
	/* Waiting for user confirmation */

	/* ^c canceled the action, or the action was resolved */
	if (len == 1 && (*input == CTRL('c') || action_handler(*input))) {
		/* ^c canceled the action, or the action was resolved */

		action_message = NULL;
		action_handler = NULL;

		return 1;
	}

	return 0;
}

static int
action_close_server(char c)
action_error(char c)
{
	/* Confirm closing a server */

	if (c == 'n' || c == 'N')
		return 1;
	UNUSED(c);

	if (c == 'y' || c == 'Y') {

		int ret;
		struct channel *c = current_channel();
		struct server *s = c->server;
	return 1;
}

		/* If closing the last server */
		if ((state.current_channel = c->server->next->channel) == c->server->channel)
			state.current_channel = state.default_channel;
static int
action_clear(char c)
{
	if (toupper(c) == 'N')
		return 1;

		if ((ret = io_sendf(s->connection, "QUIT :%s", DEFAULT_QUIT_MESG)))
			newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
	if (toupper(c) == 'Y') {
		state_channel_clear(0);
		return 1;
	}

		io_dx(s->connection);
		connection_free(s->connection);
		server_list_del(state_server_list(), s);
		server_free(s);
	return 0;
}

		draw(DRAW_ALL);
static int
action_close(char c)
{
	if (toupper(c) == 'N')
		return 1;

	if (toupper(c) == 'Y') {
		state_channel_close(0);
		return 1;
	}

	return 0;
}

void
action(int (*a_handler)(char), const char *fmt, ...)
{


@@ 343,130 314,88 @@ action(int (*a_handler)(char), const char *fmt, ...)
	va_list ap;

	va_start(ap, fmt);
	len = vsnprintf(action_buff, MAX_ACTION_MESG, fmt, ap);
	len = vsnprintf(action_buff, sizeof(action_buff), fmt, ap);
	va_end(ap);

	if (len < 0) {
		debug("vsnprintf failed");
	} else {
		action_handler = a_handler;
		action_message = action_buff;
		draw(DRAW_INPUT);
	}
}
/* Action line should be:
 *
 *
 * Find: [current result]/[(server if not current server[socket if not 6697])] : <search input> */
static int
action_find_channel(char c)
{
	/* Incremental channel search */

	/* \n, Esc, ^C cancels a search if no results are found */
	if (c == '\n' || c == 0x1b || c == CTRL('c')) {
const char*
action_message(void)
{
	return (action_handler ? action_buff : NULL);
}

		/* Confirm non-empty match */
		if (c == '\n' && search_cptr)
			channel_set_current(search_cptr);
static void
state_channel_clear(int action_confirm)
{
	struct channel *c = current_channel();

		search_buf[0] = 0;
		search_i = 0;
		search_cptr = NULL;
		return 1;
	if (action_confirm) {
		action(action_clear, "Clear buffer '%s'?   [y/n]", c->name);
	} else {
		memset(&(c->buffer), 0, sizeof(c->buffer));
		draw(DRAW_BUFFER);
	}
}

	/* ^F repeats the search forward from the current result,
	 * or resets search criteria if no match */
	if (c == CTRL('f')) {
		if (search_cptr == NULL) {
			search_buf[0] = 0;
			search_i = 0;
			action(action_find_channel, "Find: ");
			return 0;
		}
static void
state_channel_close(int action_confirm)
{
	/* Close the current channel */

		search_cptr = search_channels(search_cptr, search_buf);
	} else if (c == 0x7f && search_i) {
		/* Backspace */
		search_buf[--search_i] = 0;
		search_cptr = search_channels(current_channel(), search_buf);

	} else if (isprint(c) && search_i < (sizeof(search_buf) - 1)) {
		/* All other input */
		search_buf[search_i++] = c;
		search_buf[search_i] = 0;
		search_cptr = search_channels(current_channel(), search_buf);
	}
	int ret;
	struct channel *c = current_channel();
	struct server *s = c->server;

	/* Reprint the action message */
	if (search_cptr == NULL) {
		if (*search_buf)
			action(action_find_channel, "Find: NO MATCH -- %s", search_buf);
		else
			action(action_find_channel, "Find: ");
	} else {
		/* Found a channel */
		if (search_cptr->server == current_channel()->server) {
			action(action_find_channel, "Find: %s -- %s",
					search_cptr->name, search_buf);
		} else {
			if (!strcmp(search_cptr->server->port, "6697"))
				action(action_find_channel, "Find: %s/%s -- %s",
						search_cptr->server->host, search_cptr->name, search_buf);
			else
				action(action_find_channel, "Find: %s:%s/%s -- %s",
						search_cptr->server->host, search_cptr->server->port,
						search_cptr->name, search_buf);
		}
	if (c->type == CHANNEL_T_RIRC) {
		action(action_error, "Type :quit to exit rirc");
		return;
	}

	return 0;
}
	if (action_confirm) {

void
channel_close(struct channel *c)
{
	/* Close a channel. If the current channel is being
	 * closed, update state appropriately */
		if (c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE)
			action(action_close, "Close '%s'?   [y/n]", c->name);

		if (c->type == CHANNEL_T_SERVER)
			action(action_close, "Close server '%s'? [%d channels]   [y/n])",
				c->name, (s->clist.count - 1));

	if (c == state.default_channel) {
		newline(c, 0, "--", "Type :quit to exit rirc");
		return;
	}

	if (c->type == CHANNEL_T_SERVER) {
		/* Closing a server, confirm the number of channels being closed */
	if (c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE) {

		int num_chans = 0;
		if (s->connected && c->type == CHANNEL_T_CHANNEL && !c->parted) {
			if ((ret = io_sendf(s->connection, "PART %s :%s", c->name, DEFAULT_PART_MESG)))
				newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
		}

		while ((c = c->next)->type != CHANNEL_T_SERVER)
			num_chans++;
		channel_set_current(c->next);
		channel_list_del(&(s->clist), c);
		channel_free(c);
		return;
	}

		if (num_chans)
			action(action_close_server, "Close server '%s'? Channels: %d   [y/n]",
					c->server->host, num_chans);
		else
			action(action_close_server, "Close server '%s'?   [y/n]", c->server->host);
	} else {
		/* Closing a channel */
		if (c->type == CHANNEL_T_CHANNEL && !c->parted) {
			int ret;
			if (0 != (ret = io_sendf(c->server->connection, "PART %s", c->name))) {
				// FIXME: closing a parted channel when server is disconnected isnt an error
				newlinef(c->server->channel, 0, "sendf fail", "%s", io_err(ret));
			}
		}
	if (c->type == CHANNEL_T_SERVER) {

		/* If closing the current channel, update state to a new channel */
		if (c == current_channel()) {
			channel_set_current(c->next);
		} else {
			draw(DRAW_NAV);
		if (s->connected) {
			if ((ret = io_sendf(s->connection, "QUIT :%s", DEFAULT_QUIT_MESG)))
				newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
			io_dx(s->connection);
		}

		channel_list_del(&c->server->clist, c);
		channel_free(c);
		channel_set_current((s->next != s ? s->next->channel : state.default_channel));
		connection_free(s->connection);
		server_list_del(state_server_list(), s);
		server_free(s);
		return;
	}
}



@@ 482,8 411,8 @@ buffer_scrollback_back(struct channel *c)
	unsigned int buffer_i = b->scrollback,
	             count = 0,
	             text_w = 0,
	             cols = io_tty_cols(),
	             rows = io_tty_rows() - 4;
	             cols = state_tty_cols,
	             rows = state_tty_rows - 4;

	struct buffer_line *line = buffer_line(b, buffer_i);



@@ 524,8 453,8 @@ buffer_scrollback_forw(struct channel *c)

	unsigned int count = 0,
	             text_w = 0,
	             cols = io_tty_cols(),
	             rows = io_tty_rows() - 4;