M .builds/dev.yml => .builds/dev.yml +2 -2
@@ 28,8 28,8 @@ tasks:
set -x
./scripts/sa_coverity_get.sh coverity
./scripts/sa_coverity_run.sh coverity
- ./scripts/sa_sonarscan.sh sonarscan
- ./scripts/sa_sonarscan.sh sonarscan
+ ./scripts/sa_sonarscan_get.sh sonarscan
+ ./scripts/sa_sonarscan_run.sh sonarscan
triggers:
- action: email
M .gitignore => .gitignore +0 -6
@@ 1,11 1,5 @@
-*.gcda
-*.gcno
-*.gcov
*.gperf.out
-*.o
*.out
-*.t
-*.td
.cache
build
config.h
M Makefile => Makefile +20 -19
@@ 1,4 1,4 @@
-VERSION := 0.1.5
+VERSION := 0.1.6
PREFIX ?= /usr/local
PATH_BIN := $(DESTDIR)$(PREFIX)/bin
@@ 13,17 13,19 @@ include lib/mbedtls.Makefile
CONFIG := config.h
+CFLAGS_RIRC += -std=c11 -I. -DVERSION=\"$(VERSION)\"
+CFLAGS_RIRC += -D_POSIX_C_SOURCE=200809L
+CFLAGS_RIRC += -D_DARWIN_C_SOURCE
+
CFLAGS ?= -O2 -flto
CFLAGS += -DNDEBUG
CFLAGS_DEBUG += -O0 -g3 -Wall -Wextra -Werror
-RIRC_CFLAGS += -std=c11 -I. -DVERSION=\"$(VERSION)\"
-RIRC_CFLAGS += -D_POSIX_C_SOURCE=200809L
-RIRC_CFLAGS += -D_DARWIN_C_SOURCE
-
LDFLAGS ?= -flto
-LDFLAGS += -lpthread
+LDFLAGS += -pthread
+
+LDFLAGS_DEBUG += -pthread
SRC := $(shell find $(PATH_SRC) -name '*.c' | sort)
SRC_GPERF := $(patsubst %, %.out, $(shell find $(PATH_SRC) -name '*.gperf'))
@@ 39,25 41,24 @@ rirc: $(RIRC_LIBS) $(SRC_GPERF) $(OBJS_R)
@$(CC) $(LDFLAGS) -o $@ $(OBJS_R) $(RIRC_LIBS)
rirc.debug: $(RIRC_LIBS) $(SRC_GPERF) $(OBJS_D)
- @echo "$(CC) $(LDFLAGS) $@"
- @$(CC) $(LDFLAGS) -o $@ $(OBJS_D) $(RIRC_LIBS)
+ @echo "$(CC) $(LDFLAGS_DEBUG) $@"
+ @$(CC) $(LDFLAGS_DEBUG) -o $@ $(OBJS_D) $(RIRC_LIBS)
$(PATH_BUILD)/%.o: $(PATH_SRC)/%.c $(CONFIG) | $(PATH_BUILD)
@echo "$(CC) $(CFLAGS) $<"
- @$(CPP) $(CFLAGS) $(RIRC_CFLAGS) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
- @$(CC) $(CFLAGS) $(RIRC_CFLAGS) -c -o $@ $<
+ @$(CPP) $(CFLAGS) $(CFLAGS_RIRC) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
+ @$(CC) $(CFLAGS) $(CFLAGS_RIRC) -c -o $@ $<
$(PATH_BUILD)/%.db.o: $(PATH_SRC)/%.c $(CONFIG) | $(PATH_BUILD)
@echo "$(CC) $(CFLAGS_DEBUG) $<"
- @$(CPP) $(CFLAGS_DEBUG) $(RIRC_CFLAGS) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
- @$(CC) $(CFLAGS_DEBUG) $(RIRC_CFLAGS) -c -o $@ $<
-
-$(PATH_BUILD)/%.t: $(PATH_TEST)/%.c $(SRC_GPERF) $(CONFIG) | $(PATH_BUILD)
- @rm -f $(@:.t=.td)
- @$(CPP) $(CFLAGS_DEBUG) $(RIRC_CFLAGS) -MM -MP -MT $@ -MF $(@:.t=.t.d) $<
- @$(CC) $(CFLAGS_DEBUG) $(RIRC_CFLAGS) -c -o $(@:.t=.t.o) $<
- @$(CC) $(CFLAGS_DEBUG) $(RIRC_CFLAGS) -o $@ $(@:.t=.t.o)
- @./$@ || mv $@ $(@:.t=.td)
+ @$(CPP) $(CFLAGS_DEBUG) $(CFLAGS_RIRC) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
+ @$(CC) $(CFLAGS_DEBUG) $(CFLAGS_RIRC) -c -o $@ $<
+
+$(PATH_BUILD)/%.t: $(PATH_TEST)/%.c $(SRC_GPERF) $(CONFIG) | $(RIRC_LIBS) $(PATH_BUILD)
+ @$(CPP) $(CFLAGS_DEBUG) $(CFLAGS_RIRC) -MM -MP -MT $@ -MF $(@:.t=.t.d) $<
+ @$(CC) $(CFLAGS_DEBUG) $(CFLAGS_RIRC) -c -o $(@:.t=.t.o) $<
+ @$(CC) $(LDFLAGS_DEBUG) -o $@ $(@:.t=.t.o) $(RIRC_LIBS)
+ @{ rm -f $(@:.t=.td) && ./$@; } || mv $@ $(@:.t=.td)
$(PATH_BUILD):
@mkdir -p $(patsubst $(PATH_SRC)%, $(PATH_BUILD)%, $(shell find $(PATH_SRC) -type d))
A README => README +48 -0
@@ 0,0 1,48 @@
+ _
+ _ __(_)_ __ ___
+| '__| | '__/ __|
+| | | | | | (__
+|_| |_|_| \___|
+=================
+
+A minimalistic IRC client written in C.
+
+
+Configure
+---------
+
+Configure compile time defaults by editing `config.h`.
+To generate a default config file:
+
+ $ make config.h
+
+
+Install
+-------
+
+ $ make
+ $ make install
+
+Building rirc requires a c11 compiler, GNU gperf and GNU make.
+
+ - https://www.gnu.org/software/gperf/
+ - https://www.gnu.org/software/make/
+
+The build toolchain and install paths can be configured via
+standard environment variables, i.e.:
+
+ $CC, $CFLAGS, $LDFLAGS, $DESTDIR, $PREFIX
+
+The default install paths are:
+
+ /usr/local/bin/rirc
+ /usr/local/share/man/man1/rirc.1
+
+
+Documentation
+-------------
+
+See `rirc --help` or `man rirc` for a summary of usage. For
+complete documentation, refer to:
+
+ https://rcr.io/rirc/
D README.md => README.md +0 -109
@@ 1,109 0,0 @@
-<p align="center">
- <img src="https://raw.githubusercontent.com/rcr/rirc/master/docs/birb.jpg" alt="birb"/>
-</p>
-
----
-
-<p align="center">
- <a href="https://sonarcloud.io/dashboard?id=rirc">
- <img alt="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=rirc&metric=ncloc"/>
- </a>
- <a href="https://scan.coverity.com/projects/4940">
- <img alt="coverity" src="https://scan.coverity.com/projects/4940/badge.svg"/>
- </a>
- <a href="https://sonarcloud.io/dashboard?id=rirc">
- <img alt="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=rirc&metric=sqale_rating"/>
- </a>
- <a href="https://sonarcloud.io/dashboard?id=rirc">
- <img alt="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=rirc&metric=reliability_rating"/>
- </a>
- <a href="https://sonarcloud.io/dashboard?id=rirc">
- <img alt="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=rirc&metric=security_rating"/>
- </a>
-</p>
-
----
-
-# rirc
-
-A minimalistic irc client written in C.
-
-Connections are TLS enabled over port 6697 by default.
-
-## Building, installing:
-
-Building rirc from source requires a c11 compiler, GNU gperf and GNU make.
-
- - https://www.gnu.org/software/gperf/
- - https://www.gnu.org/software/make/
-
-```
-make
-make install
-```
-
-The build toolchain and install paths can be configured via standard
-environment variables, i.e.:
-
-```
-CC, CFLAGS, LDFLAGS, DESTDIR, PREFIX
-```
-
-## Configuring:
-
-Configure rirc by editing `config.h`. Defaults are in `config.def.h`
-
-## Usage:
-
-```
-rirc [-hv] [-s server [...]]
-
-Info:
- -h, --help Print help message and exit
- -v, --version Print rirc version and exit
-
-Server options:
- -s, --server=SERVER Connect to SERVER
- -p, --port=PORT Connect to SERVER using PORT
- -w, --pass=PASS Connect to SERVER using PASS
- -u, --username=USERNAME Connect to SERVER using USERNAME
- -r, --realname=REALNAME Connect to SERVER using REALNAME
- -n, --nicks=NICKS Comma separated list of nicks to use for SERVER
- -c, --chans=CHANNELS Comma separated list of channels to join for SERVER
-
-Server connection options:
- --ipv4 Connect to server using only ipv4 addresses
- --ipv6 Connect to server using only ipv6 addresses
- --tls-disable Set server TLS disabled
- --tls-verify=<mode> Set server TLS peer certificate verification mode
-```
-
-Commands:
-
-```
- :clear
- :close
- :connect
- :disconnect
- :quit
-```
-
-Keys:
-
-```
- ^N : go to next channel
- ^P : go to previous channel
- ^L : clear channel
- ^X : close channel
- ^C : cancel input/action
- ^U : scroll buffer up
- ^D : scroll buffer down
- ← : input cursor back
- → : input cursor forward
- ↑ : input history back
- ↓ : input history forward
-```
-
-## More info:
-
-[https://rcr.io/rirc/](https://rcr.io/rirc/)
M config.def.h => config.def.h +5 -1
@@ 110,7 110,11 @@
/* [NETWORK] */
-/* Default CA certifate file path
+/* Default CA certificate file path
+ * ("": a list of known paths is checked) */
+#define CA_CERT_FILE ""
+
+/* Default CA certificate directory path
* ("": a list of known paths is checked) */
#define CA_CERT_PATH ""
D docs/birb.jpg => docs/birb.jpg +0 -0
M docs/rirc.1 => docs/rirc.1 +94 -84
@@ 1,126 1,136 @@
'\" t
.TH RIRC 1 rirc\-VERSION
.SH NAME
-rirc \- a minimalistic irc client written in C
+rirc \- a minimalistic internet relay chat client
.SH SYNOPSIS
\fBrirc\fR [ \fB-hv\fR ] [ \fB-s\fR \fIserver\fR [ \fB...\fR ]]
-.SH DESCRIPTION
-.PP
-rirc is a lightweight Internet Relay Chat client.
-.PP
-Connections are TLS enabled over port 6697 by default.
.SH OPTIONS
-.TP 5
+.TP 4
.B "-h, --help"
Print help message and exit
.TP
.B "-v, --version"
Print rirc version and exit
-.SS Server options
-.TP 5
+.TP
.BI "-s, --server=" server
-Connect to \fIserver\fP
+Set connection \fIhostname\fP
.TP
.BI "-p, --port=" port
-Connect to \fIserver\fP using \fIport\fP
+Set connection \fIport\fP
.TP
.BI "-w, --pass=" pass
-Connect to \fIserver\fP using \fIpass\fP
+Set IRC \fIpassword\fP
.TP
.BI "-u, --username=" username
-Connect to \fIserver\fP using \fIusername\fP
+Set IRC \fIusername\fP
.TP
.BI "-r, --realname=" realname
-Connect to \fIserver\fP using \fIrealname\fP
+Set IRC \fIrealname\fP
+.TP
+.BI "-m, --mode=" modes
+Set IRC user \fImodes\fP
.TP
.BI "-n, --nicks=" nicks
-Comma separated list of \fInicks\fP to use for \fIserver\fP
+Set comma separated list of \fInicks\fP to use
.TP
.BI "-c, --chans=" chans
-Comma separated list of \fIchannels\fP to join for \fIserver\fP
-.SS Server connection options
-.TP 5
-.B --ipv4
-Connect to \fIserver\fP using only ipv4 addresses
+Set comma separated list of \fIchannels\fP to join
.TP
-.B --ipv6
-Connect to \fIserver\fP using only ipv6 addresses
+.BI --tls-cert= path
+Set TLS client certificate file \fIpath\fP
.TP
-.B --tls-disable
-Set \fIserver\fP TLS disabled, default port to 6667
+.BI --tls-ca-file= path
+Set TLS peer certificate file \fIpath\fP
+.TP
+.BI --tls-ca-path= path
+Set TLS peer certificate directory \fIpath\fP
.TP
.BI --tls-verify= mode
-Set \fIserver\fP TLS peer certificate verification \fImode\fP:
+Set TLS peer certificate verification \fImode\fP:
.EX
\(bu \fIdisabled\fP - cert is not verified
\(bu \fIoptional\fP - cert is verified, handshake continues on error
\(bu \fIrequired\fP - cert is verified, handshake is aborted on error (default)
.EE
+.TP
+.B --tls-disable
+Set TLS disabled
+.TP
+.BI --sasl= mechanism
+Authenticate with SASL \fImechanism\fP:
+.EX
+\(bu \fIexternal\fP - requires \fI--tls-cert\fP
+\(bu \fIplain\fP - requires \fI--sasl-user\fP and \fI--sasl-pass\fP
+.EE
+.TP
+.BI --sasl-user= user
+Authenticate with SASL \fIusername\fP
+.TP
+.BI --sasl-pass= pass
+Authenticate with SASL \fIpassword\fP
+.TP
+.B --ipv4
+Use IPv4 addresses only
+.TP
+.B --ipv6
+Use IPv6 addresses only
.SH USAGE
-.TS
-l .
-rirc is controlled by a combination of key bindings and commands, where:
+rirc is controlled by a combination of keys and commands, where:
<arg> denotes required arguments
[arg] denotes optional arguments
-.TE
-
-.TS
-.tab(;);
-lb l .
-Keys:
- ^N;go to next channel
- ^P;go to previous channel
- ^C;cancel current input/action
- ^L;clear current channel
- ^X;close current channel
- ^U;scroll current buffer up
- ^D;scroll current buffer down
- <left>;input cursor back
- <right>;input cursor forward
- <up>;input history back
- <down>;input history forward
-.TE
-
-.TS
-l .
-Commands beginning with ':' are interpreted as rirc commands
-and control the local client, e.g.:
-.TE
-
-.TS
-.tab(;);
-lb l .
+.TP
Commands:
- :clear;
- :close;
- :connect;
- :disconnect;
- :quit;
-.TE
-
-.TS
-l .
-Commands beginning with '/' are interpreted as IRC commands
-and will be sent to the current server, e.g.:
-.TE
-
-.TS
-.tab(;);
-lb l .
+ \fB:clear\fP
+ \fB:close\fP
+ \fB:connect\fP [hostname] [options]
+ \fB:disconnect\fP
+ \fB:quit\fP
+.TP
+Keys:
+ \fB^N\fP Go to next buffer
+ \fB^P\fP Go to previous buffer
+ \fB^L\fP Clear current buffer
+ \fB^X\fP Close current buffer
+ \fB^U\fP Scroll current buffer back
+ \fB^D\fP Scroll current buffer forward
+ \fB^C\fP Cancel current input/action
+ \fB ←\fP Input cursor back
+ \fB →\fP Input cursor forward
+ \fB ↑\fP Input history back
+ \fB ↓\fP Input history forward
+.TP
IRC commands:
- /join;[target, [targets...]]
- /me;<message>
- /nick;[nick]
- /part;[target [targets...]] [part message]
- /privmsg;<target> <message>
- /quit;[quit message]
- /raw;<message>
-.TE
-.SH AUTHORS
-.MT mail@rcr.io
-Richard 'rcr' Robbins
-.ME .
+ \fB/away\fP [away message]
+ \fB/join\fP [target, [targets...]]
+ \fB/kick\fP <channel> <nick>
+ \fB/mode\fP <target> <modes>
+ \fB/nick\fP <nick>
+ \fB/notice\fP <target> <message>
+ \fB/part\fP [target] [message]
+ \fB/privmsg\fP <target> <message>
+ \fB/quit\fP [message]
+ \fB/topic\fP [topic]
+ \fB/topic-unset\fP
+ \fB/whois\fP <target>
+.TP
+CTCP commands
+ \fB/ctcp-action\fP <target> <message>
+ \fB/ctcp-clientinfo\fP [target]
+ \fB/ctcp-finger\fP [target]
+ \fB/ctcp-ping\fP [target]
+ \fB/ctcp-source\fP [target]
+ \fB/ctcp-time\fP [target]
+ \fB/ctcp-userinfo\fP [target]
+ \fB/ctcp-version\fP [target]
+.TP
+IRCv3 commands
+ \fB/cap-ls\fP
+ \fB/cap-list\fP
.SH SEE ALSO
.UR https://rcr.io/rirc/
Additional documentation
.UE .
+.SH AUTHORS
+.MT mail@rcr.io
+Richard 'rcr' Robbins
+.ME .
M lib/mbedtls.Makefile => lib/mbedtls.Makefile +2 -2
@@ 30,8 30,8 @@ MBEDTLS_SHA_FILE := 'echo "$(MBEDTLS_VER_SHA) $(MBEDTLS_TAR)" > $(MBEDTLS_SHA)
MBEDTLS_SHA_CHECK := 'shasum -c $(MBEDTLS_SHA)'
endif
-RIRC_CFLAGS += -I$(MBEDTLS_SRC)/include/
-RIRC_CFLAGS += -DMBEDTLS_CONFIG_FILE='<$(MBEDTLS_CFG)>'
+CFLAGS_RIRC += -I$(MBEDTLS_SRC)/include/
+CFLAGS_RIRC += -DMBEDTLS_CONFIG_FILE='<$(MBEDTLS_CFG)>'
RIRC_LIBS += $(MBEDTLS_LIBS)
M scripts/build.sh => scripts/build.sh +3 -2
@@ 8,8 8,9 @@
set -e
-export CC=clang
-export LDFLAGS="-fuse-ld=lld"
+# export CC=clang
+# export LDFLAGS="-flto -fuse-ld=lld"
+# export LDFLAGS_DEBUG="-fuse-ld=lld"
if [ -x "$(command -v entr)" ]; then
ENTR="entr -c"
M scripts/coverage.sh => scripts/coverage.sh +3 -2
@@ 6,14 6,15 @@ CDIR="coverage"
export CC=gcc
export CFLAGS_DEBUG="-fprofile-arcs -ftest-coverage -fprofile-abs-path"
+export LDFLAGS_DEBUG="-fprofile-arcs"
export MAKEFLAGS="-e -j $(nproc)"
-rm -rf $CDIR && mkdir -p $CDIR
-
make clean
make check
+rm -rf $CDIR && mkdir -p $CDIR
+
GCNO=$(find build -name '*.t.gcno')
FILTER=$(cat << 'EOF'
M scripts/sanitizers_build.sh => scripts/sanitizers_build.sh +2 -2
@@ 5,14 5,14 @@ set -e
export CC=clang
export CFLAGS_DEBUG="-fsanitize=address,undefined -fno-omit-frame-pointer"
-export LDFLAGS="-fsanitize=address,undefined -fuse-ld=lld"
+export LDFLAGS_DEBUG="-fsanitize=address,undefined -fuse-ld=lld"
make clean rirc.debug
mv rirc.debug rirc.debug.address
export CFLAGS_DEBUG="-fsanitize=thread,undefined -fno-omit-frame-pointer"
-export LDFLAGS="-fsanitize=thread,undefined -fuse-ld=lld"
+export LDFLAGS_DEBUG="-fsanitize=thread,undefined -fuse-ld=lld"
make clean rirc.debug
M scripts/sanitizers_test.sh => scripts/sanitizers_test.sh +1 -1
@@ 4,7 4,7 @@ set -e
export CC=clang
export CFLAGS_DEBUG="-fsanitize=address,undefined -fno-omit-frame-pointer"
-export LDFLAGS="-fsanitize=address,undefined -fuse-ld=lld"
+export LDFLAGS_DEBUG="-fsanitize=address,undefined -fuse-ld=lld"
# for core dumps:
# export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0"
M src/components/channel.c => src/components/channel.c +3 -3
@@ 16,7 16,6 @@ channel(const char *name, enum channel_type type)
if ((c = calloc(1, sizeof(*c) + len + 1)) == NULL)
fatal("calloc: %s", strerror(errno));
- c->chanmodes_str.type = MODE_STR_CHANMODE;
c->name_len = len;
c->name = memcpy(c->_, name, len + 1);
c->type = type;
@@ 113,13 112,14 @@ void
channel_part(struct channel *c)
{
channel_reset(c);
- c->joined = 0;
c->parted = 1;
}
void
channel_reset(struct channel *c)
{
- mode_reset(&(c->chanmodes), &(c->chanmodes_str));
+ memset(&(c->chanmodes), 0, sizeof(c->chanmodes));
+ memset(&(c->chanmodes_str), 0, sizeof(c->chanmodes_str));
user_list_free(&(c->users));
+ c->joined = 0;
}
M src/components/ircv3.c => src/components/ircv3.c +19 -0
@@ 1,5 1,6 @@
#include "src/components/ircv3.h"
+#include <stdlib.h>
#include <string.h>
struct ircv3_cap*
@@ 18,6 19,7 @@ void
ircv3_caps(struct ircv3_caps *caps)
{
#define X(CAP, VAR, ATTRS) \
+ caps->VAR.val = NULL; \
caps->VAR.req = 0; \
caps->VAR.set = 0; \
caps->VAR.supported = 0; \
@@ 32,9 34,26 @@ void
ircv3_caps_reset(struct ircv3_caps *caps)
{
#define X(CAP, VAR, ATTRS) \
+ free((void *)caps->VAR.val); \
+ caps->VAR.val = NULL; \
caps->VAR.req = 0; \
caps->VAR.set = 0; \
caps->VAR.supported = 0;
IRCV3_CAPS
#undef X
}
+
+void
+ircv3_sasl(struct ircv3_sasl *sasl)
+{
+ memset(sasl, 0, sizeof(*sasl));
+
+ sasl->mech = IRCV3_SASL_MECH_NONE;
+ sasl->state = IRCV3_SASL_STATE_NONE;
+}
+
+void
+ircv3_sasl_reset(struct ircv3_sasl *sasl)
+{
+ sasl->state = IRCV3_SASL_STATE_NONE;
+}
M src/components/ircv3.h => src/components/ircv3.h +22 -1
@@ 13,7 13,8 @@
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)
+ X("multi-prefix", multi_prefix, IRCV3_CAP_AUTO) \
+ X("sasl", sasl, IRCV3_CAP_AUTO)
/* Extended by testcases */
#ifndef IRCV3_CAPS_TEST
@@ 26,6 27,7 @@
struct ircv3_cap
{
+ const char *val; /* cap key=val */
unsigned req : 1; /* cap REQ sent */
unsigned req_auto : 1; /* cap REQ sent automatically */
unsigned set : 1; /* cap is unset/set */
@@ 42,9 44,28 @@ struct ircv3_caps
#undef X
};
+struct ircv3_sasl
+{
+ enum {
+ IRCV3_SASL_MECH_NONE,
+ IRCV3_SASL_MECH_EXTERNAL,
+ IRCV3_SASL_MECH_PLAIN,
+ } mech;
+ enum {
+ IRCV3_SASL_STATE_NONE,
+ IRCV3_SASL_STATE_REQ_MECH,
+ IRCV3_SASL_STATE_AUTHENTICATED,
+ } state;
+ const char *user;
+ const char *pass;
+};
+
struct ircv3_cap* ircv3_cap_get(struct ircv3_caps*, const char*);
void ircv3_caps(struct ircv3_caps*);
void ircv3_caps_reset(struct ircv3_caps*);
+void ircv3_sasl(struct ircv3_sasl*);
+void ircv3_sasl_reset(struct ircv3_sasl*);
+
#endif
M src/components/mode.c => src/components/mode.c +124 -413
@@ 5,70 5,54 @@
#include <ctype.h>
#include <string.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
-{
- MODE_CHANMODE_PREFIX_SECRET = '@', /* chanmode 's' */
- MODE_CHANMODE_PREFIX_PRIVATE = '*', /* chanmode 'p' */
- MODE_CHANMODE_PREFIX_OTHER = '=',
-};
+#define MODE_SET_BIT(X, Y, Z) ((X) ^= (-(Z) ^ (X)) & (Y))
-static inline int mode_isset(const struct mode*, int);
-static inline uint32_t flag_bit(int);
+static int mode_isset(const struct mode*, int);
+static void mode_set(struct mode*, int, int);
+static uint32_t mode_bit(uint8_t);
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)
+static uint32_t
+mode_bit(uint8_t c)
{
- /* Test if mode flag is set, assumes valid flag */
+ static const uint32_t mode_bits[] = {
+ ['a'] = (1U << 0), ['j'] = (1U << 9), ['s'] = (1U << 18),
+ ['b'] = (1U << 1), ['k'] = (1U << 10), ['t'] = (1U << 19),
+ ['c'] = (1U << 2), ['l'] = (1U << 11), ['u'] = (1U << 20),
+ ['d'] = (1U << 3), ['m'] = (1U << 12), ['v'] = (1U << 21),
+ ['e'] = (1U << 4), ['n'] = (1U << 13), ['w'] = (1U << 22),
+ ['f'] = (1U << 5), ['o'] = (1U << 14), ['x'] = (1U << 23),
+ ['g'] = (1U << 6), ['p'] = (1U << 15), ['y'] = (1U << 24),
+ ['h'] = (1U << 7), ['q'] = (1U << 16), ['z'] = (1U << 25),
+ ['i'] = (1U << 8), ['r'] = (1U << 17), [UINT8_MAX] = 0
+ };
- if (MODE_ISLOWER(flag) && (m->lower & flag_bit(flag)))
- return 1;
+ return mode_bits[tolower(c)];
+}
- if (MODE_ISUPPER(flag) && (m->upper & flag_bit(flag)))
- return 1;
+static void
+mode_set(struct mode *m, int flag, int set)
+{
+ if (islower(flag))
+ MODE_SET_BIT(m->lower, mode_bit(flag), !!set);
- return 0;
+ if (isupper(flag))
+ MODE_SET_BIT(m->upper, mode_bit(flag), !!set);
}
-static inline uint32_t
-flag_bit(int c)
+static int
+mode_isset(const struct mode *m, int flag)
{
- /* Map input character to [az-AZ] bit flag */
-
- static const uint32_t flag_bits[] = {
- 1U << 0, /* a */ 1U << 1, /* b */ 1U << 2, /* c */
- 1U << 3, /* d */ 1U << 4, /* e */ 1U << 5, /* f */
- 1U << 6, /* g */ 1U << 7, /* h */ 1U << 8, /* i */
- 1U << 9, /* j */ 1U << 10, /* k */ 1U << 11, /* l */
- 1U << 12, /* m */ 1U << 13, /* n */ 1U << 14, /* o */
- 1U << 15, /* p */ 1U << 16, /* q */ 1U << 17, /* r */
- 1U << 18, /* s */ 1U << 19, /* t */ 1U << 20, /* u */
- 1U << 21, /* v */ 1U << 22, /* w */ 1U << 23, /* x */
- 1U << 24, /* y */ 1U << 25, /* z */
- };
-
- if (MODE_ISLOWER(c))
- return flag_bits[c - 'a'];
+ if (islower(flag) && (m->lower & mode_bit(flag)))
+ return 1;
- if (MODE_ISUPPER(c))
- return flag_bits[c - 'A'];
+ if (isupper(flag) && (m->upper & mode_bit(flag)))
+ return 1;
return 0;
}
@@ 99,10 83,9 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
*
* b - set/remove ban mask to keep users out;
* e - set/remove an exception mask to override a ban mask;
- * I - set/remove an invitation mask to automatically override the
- * invite-only flag;
+ * I - set/remove an invitation mask to automatically override the invite-only flag;
*
- * Usermodes (RFC2811, section 3.1.5)
+ * Usermodes (RFC2812, section 3.1.5)
*
* a - user is flagged as away;
* i - marks a users as invisible;
@@ 111,33 94,17 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
* o - operator flag;
* O - local operator flag;
* s - marks a user for receipt of server notices.
- *
- * MODES (RFC2811, section 3.2.3)
- *
- * "Note that there is a maximum limit of three (3) changes per command
- * for modes that take a parameter."
- *
- * Note: PREFIX, MODES and CHANMODES are ubiquitous additions to the IRC
- * protocol given by numeric 005 (RPL_ISUPPORT). As such,
- * they've been interpreted here in terms of A,B,C,D subcategories
- * for the sake of default settings. Numeric 319 (RPL_WHOISCHANNELS)
- * states chanmode user prefixes map o,v to @,+ respectively.
*/
switch (cfg_type) {
case MODE_CFG_DEFAULTS:
- *cfg = (struct mode_cfg)
- {
- .PREFIX = {
- .F = "ov",
- .T = "@+"
- },
- .MODES = 3
- };
+ memset(cfg, 0, sizeof(*cfg));
+ (void) snprintf(cfg->PREFIX.F, sizeof(cfg->PREFIX.F), "ov");
+ (void) snprintf(cfg->PREFIX.T, sizeof(cfg->PREFIX.T), "@+");
mode_cfg_chanmodes(cfg, "OovaimnqpsrtklbeI");
mode_cfg_usermodes(cfg, "aiwroOs");
- mode_cfg_subtypes(cfg, "beI,k,l,aimnqpsrtO");
+ mode_cfg_subtypes(cfg, "IObe,k,l,aimnqpsrt");
break;
case MODE_CFG_CHANMODES:
@@ 152,9 119,6 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
case MODE_CFG_PREFIX:
return mode_cfg_prefix(cfg, cfg_str);
- case MODE_CFG_MODES:
- return mode_cfg_modes(cfg, cfg_str);
-
default:
fatal("mode configuration type unknown: %d", cfg_type);
}
@@ 163,127 127,41 @@ mode_cfg(struct mode_cfg *cfg, const char *cfg_str, enum mode_cfg_type cfg_type)
}
enum mode_err
-mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
+mode_chanmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
- /* Set/unset chanmode flags
- *
- * Only CHANMODE subtypes B,C,D set/unset flags for the channel
- *
- * ---
- *
- * RFC2812, section 5.1, numeric reply 353 (RPL_NAMREPLY)
- *
- * "@" is used for secret channels, ('s' flag)
- * "*" for private channels, and ('p' flag)
- * "=" for others (public channels).
- *
- * RFC2811, section 4.2.6 Private and Secret Channels
- *
- * The channel flag 'p' is used to mark a channel "private" and the
- * channel flag 's' to mark a channel "secret". Both properties are
- * similar and conceal the existence of the channel from other users.
- *
- * This means that there is no way of getting this channel's name from
- * the server without being a member. In other words, these channels
- * MUST be omitted from replies to queries like the WHOIS command.
- *
- * When a channel is "secret", in addition to the restriction above, the
- * server will act as if the channel does not exist for queries like the
- * TOPIC, LIST, NAMES commands. Note that there is one exception to
- * this rule: servers will correctly reply to the MODE command.
- * Finally, secret channels are not accounted for in the reply to the
- * LUSERS command (See "Internet Relay Chat: Client Protocol" [IRC-
- * CLIENT]) when the <mask> parameter is specified.
- *
- * The channel flags 'p' and 's' MUST NOT both be set at the same time.
- * If a MODE message originating from a server sets the flag 'p' and the
- * flag 's' is already set for the channel, the change is silently
- * ignored. This should only happen during a split healing phase
- * (mentioned in the "IRC Server Protocol" document [IRC-SERVER]).
- */
-
- if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
- return MODE_ERR_INVALID_SET;
+ /* Set/unset chanmode flags */
if (!mode_isset(&(cfg->chanmodes), flag))
return MODE_ERR_INVALID_FLAG;
- if (set == MODE_SET_ON && mode_isset(m, flag))
- return MODE_ERR_DUPLICATE;
-
- /* Mode subtypes A don't set a flag */
if (mode_isset(&(cfg->CHANMODES.A), flag))
return MODE_ERR_NONE;
- if (flag != 's' && flag != 'p') {
-
- uint32_t bit = flag_bit(flag);
-
- if (MODE_ISLOWER(flag))
- MODE_SET(m->lower, bit, set);
- else
- MODE_SET(m->upper, bit, set);
-
- } else if (flag == 'p') {
-
- /* Silently ignore */
- if (mode_isset(m, 's'))
- return MODE_ERR_NONE;
-
- if (set == MODE_SET_OFF) {
- MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);
-
- m->prefix = MODE_CHANMODE_PREFIX_OTHER;
- }
-
- if (set == MODE_SET_ON) {
- MODE_SET(m->lower, flag_bit('p'), MODE_SET_ON);
-
- m->prefix = MODE_CHANMODE_PREFIX_PRIVATE;
- }
-
- } else if (flag == 's') {
-
- 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 == MODE_SET_ON) {
- MODE_SET(m->lower, flag_bit('s'), MODE_SET_ON);
- MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);
-
- m->prefix = MODE_CHANMODE_PREFIX_SECRET;
- }
- }
+ mode_set(m, flag, set);
return MODE_ERR_NONE;
}
enum mode_err
-mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
+mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
- /* Set/unset prfxmode flags and mode prefix */
+ /* Set/unset prfxmode flag or prefix */
- uint32_t bit;
+ const char *f = cfg->PREFIX.F;
+ const char *t = cfg->PREFIX.T;
- if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
- return MODE_ERR_INVALID_SET;
+ while (*f && *t && *f != flag && *t != flag) {
+ f++;
+ t++;
+ }
- if (!strchr(cfg->PREFIX.F, flag))
+ if (!*f || !*t)
return MODE_ERR_INVALID_FLAG;
- bit = flag_bit(flag);
+ mode_set(m, *f, set);
- if (MODE_ISLOWER(flag))
- MODE_SET(m->lower, bit, set);
- else
- MODE_SET(m->upper, bit, set);
-
- const char *f = cfg->PREFIX.F,
- *t = cfg->PREFIX.T;
+ f = cfg->PREFIX.F;
+ t = cfg->PREFIX.T;
while (*f) {
@@ 300,140 178,42 @@ mode_prfxmode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mod
}
enum mode_err
-mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, enum mode_set set)
+mode_usermode_set(struct mode *m, const struct mode_cfg *cfg, int flag, int set)
{
/* Set/unset usermode flags */
- uint32_t bit;
-
- if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
- return MODE_ERR_INVALID_SET;
-
if (!mode_isset(&(cfg->usermodes), flag))
return MODE_ERR_INVALID_FLAG;
- bit = flag_bit(flag);
-
- if (MODE_ISLOWER(flag))
- MODE_SET(m->lower, bit, set);
- else
- MODE_SET(m->upper, bit, set);
-
- return MODE_ERR_NONE;
-}
-
-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.:
- *
- * - '@' sets 's', unsets 'p'
- * - '*' sets 'p'
- * - '=' sets neither
- *
- * All other prefixes are invalid.
- * Prefixes may override by precendece, but are silentyly ignored otherwise */
-
- (void)(cfg);
-
- /* If 's' is set, all other settings are silently ignored */
- if (m->prefix == MODE_CHANMODE_PREFIX_SECRET)
- return MODE_ERR_NONE;
-
- /* If 'p' is set, only SECRET prefix is accepted */
- if (m->prefix == MODE_CHANMODE_PREFIX_PRIVATE && flag != MODE_CHANMODE_PREFIX_SECRET)
- return MODE_ERR_NONE;
-
- /* Otherwise, all valid prefixes can be set */
- switch (flag) {
- case MODE_CHANMODE_PREFIX_SECRET:
- MODE_SET(m->lower, flag_bit('p'), MODE_SET_OFF);
- MODE_SET(m->lower, flag_bit('s'), MODE_SET_ON);
- break;
- case MODE_CHANMODE_PREFIX_PRIVATE:
- MODE_SET(m->lower, flag_bit('p'), MODE_SET_ON);
- break;
- case MODE_CHANMODE_PREFIX_OTHER:
- break;
- default:
- return MODE_ERR_INVALID_PREFIX;
- }
-
- m->prefix = flag;
-
- return MODE_ERR_NONE;
-}
-
-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.:
- *
- * - if "ov" maps to "@+", then:
- * - prfxmode_prefix(cfg, mode, '@') sets mode flag 'o'
- * - prfxmode_prefix(cfg, mode, '+') sets mode flag 'v'
- */
-
- uint32_t bit;
-
- const char *f = cfg->PREFIX.F,
- *t = cfg->PREFIX.T;
-
- while (*t && *t != flag) {
- f++;
- t++;
- }
-
- if (*t == 0)
- return MODE_ERR_INVALID_PREFIX;
-
- bit = flag_bit(*f);
-
- if (MODE_ISLOWER(*f))
- MODE_SET(m->lower, bit, MODE_SET_ON);
- else
- MODE_SET(m->upper, bit, MODE_SET_ON);
-
- f = cfg->PREFIX.F,
- t = cfg->PREFIX.T;
-
- while (!mode_isset(m, *f)) {
- f++;
- t++;
- }
-
- m->prefix = *t;
+ mode_set(m, flag, set);
return MODE_ERR_NONE;
}
const char*
-mode_str(const struct mode *m, struct mode_str *m_str)
+mode_str(const struct mode *m, struct mode_str *m_str, enum mode_str_type type)
{
/* Write the mode bits to a mode string */
- char c;
char *str = m_str->str;
- uint32_t lower = m->lower,
- upper = m->upper;
+ uint32_t lower = m->lower;
+ uint32_t upper = m->upper;
- switch (m_str->type) {
+ switch (type) {
case MODE_STR_CHANMODE:
case MODE_STR_USERMODE:
case MODE_STR_PRFXMODE:
break;
- case MODE_STR_UNSET:
- fatal("mode_str type not set");
default:
fatal("mode_str type unknown");
}
- for (c = 'a'; c <= 'z' && lower; c++, lower >>= 1)
+ for (char c = 'a'; c <= 'z' && lower; c++, lower >>= 1)
if (lower & 1)
*str++ = c;
- for (c = 'A'; c <= 'Z' && upper; c++, upper >>= 1)
+ for (char c = 'A'; c <= 'Z' && upper; c++, upper >>= 1)
if (upper & 1)
*str++ = c;
@@ 442,22 222,6 @@ mode_str(const struct mode *m, struct mode_str *m_str)
return m_str->str;
}
-void
-mode_reset(struct mode *m, struct mode_str *s)
-{
- /* Set mode and mode_str to initial state */
-
- if (!m || !s)
- fatal("mode or mode_str is null");
-
- enum mode_str_type type = s->type;
-
- memset(m, 0, sizeof(*m));
- memset(s, 0, sizeof(*s));
-
- s->type = type;
-}
-
static enum mode_err
mode_cfg_chanmodes(struct mode_cfg *cfg, const char *str)
{
@@ 471,18 235,13 @@ mode_cfg_chanmodes(struct mode_cfg *cfg, const char *str)
while ((c = *str++)) {
- uint32_t bit;
-
- if ((bit = flag_bit(c)) == 0)
- continue; /* TODO: aggregate warnings, invalid flag */
+ if (!mode_bit(c))
+ continue;
if (mode_isset(chanmodes, c))
- continue; /* TODO: aggregate warnings, duplicate flag */
+ continue;
- if (MODE_ISLOWER(c))
- MODE_SET(chanmodes->lower, bit, MODE_SET_ON);
- else
- MODE_SET(chanmodes->upper, bit, MODE_SET_ON);
+ mode_set(chanmodes, c, 1);
}
return MODE_ERR_NONE;
@@ 501,18 260,13 @@ mode_cfg_usermodes(struct mode_cfg *cfg, const char *str)
while ((c = *str++)) {
- uint32_t bit;
-
- if ((bit = flag_bit(c)) == 0)
- continue; /* TODO: aggregate warnings, invalid flag */
+ if (!mode_bit(c))
+ continue;
if (mode_isset(usermodes, c))
- continue; /* TODO: aggregate warnings, duplicate flag */
+ continue;
- if (MODE_ISLOWER(c))
- MODE_SET(usermodes->lower, bit, MODE_SET_ON);
- else
- MODE_SET(usermodes->upper, bit, MODE_SET_ON);
+ mode_set(usermodes, c, 1);
}
return MODE_ERR_NONE;
@@ 524,10 278,10 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
/* Parse and configure CHANMODE subtypes, e.g.:
*
* "abc,d,ef,xyz" sets mode bits:
- * - A = a | b | c
+ * - A = abc
* - B = d
- * - C = e | f
- * - D = x | y | z
+ * - C = ef
+ * - D = xyz
*/
char c;
@@ 539,20 293,17 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
&(cfg->CHANMODES.D)
};
- struct mode duplicates, *setting = subtypes[0];
+ struct mode *setting = subtypes[0];
- memset(&(cfg->CHANMODES.A), 0, sizeof (struct mode));
- memset(&(cfg->CHANMODES.B), 0, sizeof (struct mode));
- memset(&(cfg->CHANMODES.C), 0, sizeof (struct mode));
- memset(&(cfg->CHANMODES.D), 0, sizeof (struct mode));
- memset(&duplicates, 0, sizeof (struct mode));
+ memset(&(cfg->CHANMODES.A), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.B), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.C), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.D), 0, sizeof(struct mode));
unsigned commas = 0;
while ((c = *str++)) {
- uint32_t bit;
-
if (c == ',') {
switch (commas) {
case 0:
@@ 561,26 312,26 @@ mode_cfg_subtypes(struct mode_cfg *cfg, const char *str)
setting = subtypes[++commas];
continue;
default:
- return MODE_ERR_INVALID_CONFIG;
+ goto error;
}
}
- if ((bit = flag_bit(c)) == 0)
- continue; /* TODO: aggregate warnings, invalid flag */
-
- if (mode_isset(&duplicates, c))
- continue; /* TODO: aggregate warnings, duplicate flag */
+ if (!mode_bit(c))
+ goto error;
- if (MODE_ISLOWER(c)) {
- MODE_SET(duplicates.lower, bit, MODE_SET_ON);
- MODE_SET(setting->lower, bit, MODE_SET_ON);
- } else {
- MODE_SET(duplicates.upper, bit, MODE_SET_ON);
- MODE_SET(setting->upper, bit, MODE_SET_ON);
- }
+ mode_set(setting, c, 1);
}
return MODE_ERR_NONE;
+
+error:
+
+ memset(&(cfg->CHANMODES.A), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.B), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.C), 0, sizeof(struct mode));
+ memset(&(cfg->CHANMODES.D), 0, sizeof(struct mode));
+
+ return MODE_ERR_INVALID_CONFIG;
}
static enum mode_err
@@ 594,17 345,16 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
* - c -> #
*/
- char *str_f, cf,
- *str_t, ct,
- *cfg_f = cfg->PREFIX.F,
- *cfg_t = cfg->PREFIX.T,
- _str[strlen(str) + 1];
+ char *cfg_f = cfg->PREFIX.F;
+ char *cfg_t = cfg->PREFIX.T;
+ char *dup = strdup(str);
+ char *str_f;
+ char *str_t;
- struct mode duplicates;
+ memset(cfg->PREFIX.F, 0, sizeof(cfg->PREFIX.F));
+ memset(cfg->PREFIX.T, 0, sizeof(cfg->PREFIX.T));
- memcpy(_str, str, sizeof(_str));
-
- if (*(str_f = _str) != '(')
+ if (*(str_f = dup) != '(')
goto error;
if (!(str_t = strchr(str_f, ')')))
@@ 616,32 366,20 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
if (strlen(str_f) != strlen(str_t))
goto error;
- memset(&duplicates, 0, sizeof duplicates);
-
while (*str_f) {
- uint32_t bit;
-
- cf = *str_f++;
- ct = *str_t++;
+ char cf = *str_f++;
+ char ct = *str_t++;
- /* Check printable prefix */
- if (!(isgraph(ct)))
+ if (!mode_bit(cf))
goto error;
- /* Check valid flag */
- if ((bit = flag_bit(cf)) == 0)
+ if (!(isgraph(ct)))
goto error;
- /* Check duplicates */
- if (mode_isset(&duplicates, cf))
+ if (strchr(cfg->PREFIX.F, cf))
goto error;
- if (MODE_ISLOWER(cf))
- MODE_SET(duplicates.lower, bit, MODE_SET_ON);
- else
- MODE_SET(duplicates.upper, bit, MODE_SET_ON);
-
*cfg_f++ = cf;
*cfg_t++ = ct;
}
@@ 649,6 387,8 @@ mode_cfg_prefix(struct mode_cfg *cfg, const char *str)
*cfg_f = 0;
*cfg_t = 0;
+ free(dup);
+
return MODE_ERR_NONE;
error:
@@ 656,62 396,33 @@ error:
*(cfg->PREFIX.F) = 0;
*(cfg->PREFIX.T) = 0;
- return MODE_ERR_INVALID_CONFIG;
-}
+ free(dup);
-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] */
-
- unsigned modes = 0;
-
- for (; modes < 100 && *str; str++) {
- if (isdigit(*str))
- modes = modes * 10 + (*str - '0');
- else
- return MODE_ERR_INVALID_CONFIG;
- }
-
- if (!(modes > 0 && modes < 100))
- return MODE_ERR_INVALID_CONFIG;
-
- cfg->MODES = modes;
-
- return MODE_ERR_NONE;
+ return MODE_ERR_INVALID_CONFIG;
}
-enum chanmode_flag_type
-chanmode_type(const struct mode_cfg *cfg, enum mode_set set, int flag)
+enum mode_type
+mode_type(const struct mode_cfg *cfg, int flag, int set)
{
- /* Return the chanmode flag type specified by config */
-
- if (!(set == MODE_SET_ON || set == MODE_SET_OFF))
- return MODE_FLAG_INVALID_SET;
-
- if (mode_isset(&(cfg->chanmodes), flag)) {
-
- if (strchr(cfg->PREFIX.F, flag))
- return MODE_FLAG_PREFIX;
-
- if (mode_isset(&(cfg->CHANMODES.A), flag))
- return MODE_FLAG_CHANMODE_PARAM;
+ /* Chanmode PREFIX */
+ if (strchr(cfg->PREFIX.F, flag))
+ return MODE_FLAG_PREFIX;
- if (mode_isset(&(cfg->CHANMODES.B), flag))
- return MODE_FLAG_CHANMODE_PARAM;
-
- if (mode_isset(&(cfg->CHANMODES.C), flag)) {
+ /* Chanmode subtype A, Always has a parameter. */
+ if (mode_isset(&(cfg->CHANMODES.A), flag))
+ return MODE_FLAG_CHANMODE_PARAM;
- if (set == MODE_SET_ON)
- return MODE_FLAG_CHANMODE_PARAM;
+ /* Chanmode subtype B, Always has a parameter. */
+ if (mode_isset(&(cfg->CHANMODES.B), flag))
+ return MODE_FLAG_CHANMODE_PARAM;
- if (set == MODE_SET_OFF)
- return MODE_FLAG_CHANMODE;
- }
+ /* Chanmode subtype C, Only has a parameter when set. */
+ if (mode_isset(&(cfg->CHANMODES.C), flag))
+ return (set ? MODE_FLAG_CHANMODE_PARAM : MODE_FLAG_CHANMODE);
- if (mode_isset(&(cfg->CHANMODES.D), flag))
- return MODE_FLAG_CHANMODE;
- }
+ /* Chanmode subtype D, Never has a parameter. */
+ if (mode_isset(&(cfg->CHANMODES.D), flag))
+ return MODE_FLAG_CHANMODE;
return MODE_FLAG_INVALID_FLAG;
}
M src/components/mode.h => src/components/mode.h +24 -48
@@ 30,9 30,6 @@
*
* PREFIX modes are not included in CHANMODES
*
- * MODES specifies the maximum number of channel modes with parameter
- * allowed per MODE command
- *
* Numeric 353 (RPL_NAMREPLY) sets chanmode and prfxmode for users on a channel
* by providing the prefix character rather than the flag
*/
@@ 42,47 39,38 @@
/* [azAZ] */
#define MODE_STR_LEN 26 * 2
-#define MODE_EMPTY (struct mode) \
-{ \
- .prefix = 0, \
- .lower = 0, \
- .upper = 0, \
-}
+#define MODE_EMPTY (struct mode) { 0 }
enum mode_err
{
- MODE_ERR_DUPLICATE = -5,
- MODE_ERR_INVALID_CONFIG = -4,
- MODE_ERR_INVALID_PREFIX = -3,
- MODE_ERR_INVALID_FLAG = -2,
- MODE_ERR_INVALID_SET = -1,
+ MODE_ERR_INVALID_CONFIG = -3,
+ MODE_ERR_INVALID_PREFIX = -2,
+ MODE_ERR_INVALID_FLAG = -1,
MODE_ERR_NONE
};
+enum mode_type
+{
+ MODE_FLAG_INVALID_FLAG,
+ MODE_FLAG_CHANMODE, /* Chanmode flag without parameter */
+ MODE_FLAG_CHANMODE_PARAM, /* Chanmode flag with parameter */
+ MODE_FLAG_PREFIX, /* Chanmode flag that sets prfxmode */
+};
+
enum mode_cfg_type
{
MODE_CFG_DEFAULTS, /* Set RFC2811 mode defaults */
MODE_CFG_CHANMODES, /* Set numeric 004 chanmdoes string */
MODE_CFG_USERMODES, /* Set numeric 004 usermodes string */
- MODE_CFG_PREFIX, /* Set numeric 005 PREFIX */
MODE_CFG_SUBTYPES, /* Set numeric 005 CHANMODES subtypes */
- MODE_CFG_MODES, /* Set numeric 005 MODES */
-};
-
-enum mode_set
-{
- MODE_SET_OFF = 0,
- MODE_SET_ON = 1,
- MODE_SET_INVALID,
+ MODE_CFG_PREFIX, /* Set numeric 005 PREFIX */
};
-enum chanmode_flag_type
+enum mode_str_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_STR_CHANMODE,
+ MODE_STR_USERMODE,
+ MODE_STR_PRFXMODE,
};
struct mode
@@ 94,12 82,11 @@ struct mode
struct mode_cfg
{
- unsigned MODES; /* Numeric 005 MODES */
struct mode chanmodes; /* Numeric 004 chanmodes string */
struct mode usermodes; /* Numeric 004 usermodes string */
- struct
+ struct /* Numeric 005 CHANMODES substrings */
{
- struct mode A; /* Numeric 005 CHANMODES substrings */
+ struct mode A;
struct mode B;
struct mode C;
struct mode D;
@@ 114,24 101,13 @@ struct mode_cfg
struct mode_str
{
char str[MODE_STR_LEN + 1];
- enum mode_str_type
- {
- MODE_STR_UNSET = 0,
- MODE_STR_CHANMODE,
- MODE_STR_USERMODE,
- MODE_STR_PRFXMODE,
- MODE_STR_T_SIZE
- } type;
};
-const char* mode_str(const struct mode*, struct mode_str*);
-enum chanmode_flag_type chanmode_type(const struct mode_cfg*, enum mode_set, int);
+const char* mode_str(const struct mode*, struct mode_str*, enum mode_str_type);
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*);
+enum mode_err mode_chanmode_set(struct mode*, const struct mode_cfg*, int, int);
+enum mode_err mode_prfxmode_set(struct mode*, const struct mode_cfg*, int, int);
+enum mode_err mode_usermode_set(struct mode*, const struct mode_cfg*, int, int);
+enum mode_type mode_type(const struct mode_cfg*, int, int);
#endif
M src/components/server.c => src/components/server.c +42 -17
@@ 7,11 7,11 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#define HANDLED_005 \
X(CASEMAPPING) \
X(CHANMODES) \
- X(MODES) \
X(PREFIX)
struct opt
@@ 28,21 28,30 @@ HANDLED_005
#undef X
struct server*
-server(const char *host, const char *port, const char *pass, const char *user, const char *real)
+server(
+ const char *host,
+ const char *port,
+ const char *pass,
+ const char *username,
+ const char *realname,
+ const char *mode)
{
struct server *s;
if ((s = calloc(1, sizeof(*s))) == NULL)
fatal("calloc: %s", strerror(errno));
- s->host = strdup(host);
- s->port = strdup(port);
- s->pass = pass ? strdup(pass) : NULL;
- s->username = strdup(user);
- s->realname = strdup(real);
+ s->host = strdup(host);
+ s->port = strdup(port);
+ s->username = strdup(username);
+ s->realname = strdup(realname);
+
+ s->pass = (pass ? strdup(pass) : NULL);
+ s->mode = (mode ? strdup(mode) : NULL);
+
s->casemapping = CASEMAPPING_RFC1459;
- s->mode_str.type = MODE_STR_USERMODE;
ircv3_caps(&(s->ircv3_caps));
+ ircv3_sasl(&(s->ircv3_sasl));
mode_cfg(&(s->mode_cfg), NULL, MODE_CFG_DEFAULTS);
s->channel = channel(host, CHANNEL_T_SERVER);
@@ 130,7 139,9 @@ void
server_reset(struct server *s)
{
ircv3_caps_reset(&(s->ircv3_caps));
- mode_reset(&(s->usermodes), &(s->mode_str));
+ ircv3_sasl_reset(&(s->ircv3_sasl));
+ memset(&(s->usermodes), 0, sizeof(s->usermodes));
+ memset(&(s->mode_str), 0, sizeof(s->mode_str));
s->ping = 0;
s->quitting = 0;
s->registered = 0;
@@ 142,16 153,17 @@ server_free(struct server *s)
{
channel_list_free(&(s->clist));
- user_list_free(&(s->ignore));
-
free((void *)s->host);
free((void *)s->port);
free((void *)s->pass);
free((void *)s->username);
free((void *)s->realname);
+ free((void *)s->mode);
free((void *)s->nick);
free((void *)s->nicks.base);
free((void *)s->nicks.set);
+ free((void *)s->ircv3_sasl.user);
+ free((void *)s->ircv3_sasl.pass);
free(s);
}
@@ 312,6 324,25 @@ server_set_005(struct server *s, char *str)
}
}
+void
+server_set_sasl(struct server *s, const char *mech, const char *user, const char *pass)
+{
+ free((void *)s->ircv3_sasl.user);
+ free((void *)s->ircv3_sasl.pass);
+
+ if (!strcasecmp(mech, "EXTERNAL")) {
+ s->ircv3_sasl.mech = IRCV3_SASL_MECH_EXTERNAL;
+ s->ircv3_sasl.user = NULL;
+ s->ircv3_sasl.pass = NULL;
+ }
+
+ if (!strcasecmp(mech, "PLAIN")) {
+ s->ircv3_sasl.mech = IRCV3_SASL_MECH_PLAIN;
+ s->ircv3_sasl.user = (user ? strdup(user) : NULL);
+ s->ircv3_sasl.pass = (pass ? strdup(pass) : NULL);
+ }
+}
+
static int
server_cmp(const struct server *s, const char *host, const char *port)
{
@@ 424,12 455,6 @@ server_set_CHANMODES(struct server *s, char *val)
}
static int
-server_set_MODES(struct server *s, char *val)
-{
- return mode_cfg(&(s->mode_cfg), val, MODE_CFG_MODES) != MODE_ERR_NONE;
-}
-
-static int
server_set_PREFIX(struct server *s, char *val)
{
return mode_cfg(&(s->mode_cfg), val, MODE_CFG_PREFIX) != MODE_ERR_NONE;
M src/components/server.h => src/components/server.h +5 -2
@@ 17,6 17,7 @@ struct server
const char *username;
const char *realname;
const char *nick;
+ const char *mode;
enum casemapping casemapping;
struct {
size_t next;
@@ 27,12 28,12 @@ struct server
struct channel *channel;
struct channel_list clist;
struct ircv3_caps ircv3_caps;
+ struct ircv3_sasl ircv3_sasl;
struct mode usermodes;
struct mode_str mode_str;
struct mode_cfg mode_cfg;
struct server *next;
struct server *prev;
- struct user_list ignore;
unsigned ping;
unsigned connected : 1;
unsigned quitting : 1;
@@ 58,7 59,8 @@ struct server* server(
const char*, /* port */
const char*, /* pass */
const char*, /* username */
- const char*); /* realname */
+ const char*, /* realname */
+ const char*); /* mode */
struct server* server_list_add(struct server_list*, struct server*);
struct server* server_list_del(struct server_list*, struct server*);
@@ 68,6 70,7 @@ int server_set_chans(struct server*, const char*);
int server_set_nicks(struct server*, const char*);
void server_set_004(struct server*, char*);
void server_set_005(struct server*, char*);
+void server_set_sasl(struct server*, const char*, const char*, const char*);
void server_nick_set(struct server*, const char*);
void server_nicks_next(struct server*);
M src/draw.c => src/draw.c +18 -9
@@ 438,8 438,19 @@ print_text:
draw_attr_bg(text_bg);
draw_attr_fg(text_fg);
- for (unsigned i = 0; i < (text_p2 - text_p1); i++)
- draw_char(text_p1[i]);
+ for (unsigned i = 0; i < (text_p2 - text_p1); i++) {
+ switch (text_p1[i]) {
+ case 0x02:
+ case 0x03:
+ case 0x0F:
+ case 0x16:
+ case 0x1D:
+ case 0x1F:
+ break;
+ default:
+ draw_char(text_p1[i]);
+ }
+ }
draw_attr_reset();
}
@@ 677,7 688,11 @@ draw_status(struct channel *c)
if (c->type == CHANNEL_T_CHANNEL && c->joined) {
if (!drawf(&cols, STATUS_SEP_HORZ))
return;
- if (!drawf(&cols, "[+%s %u]", c->chanmodes_str.str, c->users.count))
+ if (!drawf(&cols, "[%s%s%s%u]",
+ (*(c->chanmodes_str.str) ? "+" : ""),
+ (*(c->chanmodes_str.str) ? c->chanmodes_str.str : ""),
+ (*(c->chanmodes_str.str) ? " " : ""),
+ c->users.count))
return;
}
@@ 739,7 754,6 @@ drawf(unsigned *cols_p, const char *fmt, ...)
* %a -- attribute reset
* %b -- set background colour attribute
* %f -- set foreground colour attribute
- * %c -- output char
* %d -- output signed integer
* %u -- output unsigned integer
* %s -- output string
@@ 767,10 781,6 @@ drawf(unsigned *cols_p, const char *fmt, ...)
case 'f':
draw_attr_fg(va_arg(arg, int));
break;
- case 'c':
- draw_char(va_arg(arg, int));
- cols--;
- break;
case 'd':
(void) snprintf(buf, sizeof(buf), "%d", va_arg(arg, int));
cols -= (unsigned) printf("%.*s", cols, buf);
@@ 786,7 796,6 @@ drawf(unsigned *cols_p, const char *fmt, ...)
} while (UTF8_CONT(*str));
}
break;
- case '%':
default:
fatal("unknown drawf format character '%c'", c);
}
M src/handlers/irc_recv.c => src/handlers/irc_recv.c +92 -79
@@ 1,16 1,15 @@
#include "src/handlers/irc_recv.h"
+#include "config.h"
#include "src/components/server.h"
+#include "src/draw.h"
#include "src/handlers/irc_ctcp.h"
#include "src/handlers/irc_recv.gperf.out"
#include "src/handlers/ircv3.h"
-#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"
-#include "config.h"
-
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
@@ 210,6 209,15 @@ static const irc_recv_f irc_numerics[] = {
[704] = irc_generic_info, /* RPL_HELPSTART */
[705] = irc_generic_info, /* RPL_HELP */
[706] = irc_generic_ignore, /* RPL_ENDOFHELP */
+ [900] = ircv3_numeric_900, /* IRCv3 RPL_LOGGEDIN */
+ [901] = ircv3_numeric_901, /* IRCv3 RPL_LOGGEDOUT */
+ [902] = ircv3_numeric_902, /* IRCv3 ERR_NICKLOCKED */
+ [903] = ircv3_numeric_903, /* IRCv3 RPL_SASLSUCCESS */
+ [904] = ircv3_numeric_904, /* IRCv3 ERR_SASLFAIL */
+ [905] = ircv3_numeric_905, /* IRCv3 ERR_SASLTOOLONG */
+ [906] = ircv3_numeric_906, /* IRCv3 ERR_SASLABORTED */
+ [907] = ircv3_numeric_907, /* IRCv3 ERR_SASLALREADY */
+ [908] = ircv3_numeric_908, /* IRCv3 RPL_SASLMECHS */
[1000] = NULL /* Out of range */
};
@@ 313,16 321,19 @@ irc_numeric_001(struct server *s, struct irc_message *m)
s->registered = 1;
+ if (irc_message_split(m, ¶ms, &trailing))
+ server_info(s, "%s", trailing);
+
+ server_info(s, "You are known as %s", s->nick);
+
+ if (s->mode)
+ sendf(s, "MODE %s +%s", s->nick, s->mode);
+
do {
if (c->type == CHANNEL_T_CHANNEL && !c->parted)
sendf(s, "JOIN %s", c->name);
} while ((c = c->next) != s->channel);
- if (irc_message_split(m, ¶ms, &trailing))
- newlinef(s->channel, 0, FROM_INFO, "%s", trailing);
-
- server_info(s, "You are known as %s", s->nick);
-
return 0;
}
@@ 335,9 346,9 @@ irc_numeric_004(struct server *s, struct irc_message *m)
const char *trailing;
if (irc_message_split(m, ¶ms, &trailing))
- newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", params, trailing);
+ server_info(s, "%s ~ %s", params, trailing);
else
- newlinef(s->channel, 0, FROM_INFO, "%s", params);
+ server_info(s, "%s", params);
server_set_004(s, m->params);
@@ 353,9 364,9 @@ irc_numeric_005(struct server *s, struct irc_message *m)
const char *trailing;
if (irc_message_split(m, ¶ms, &trailing))
- newlinef(s->channel, 0, FROM_INFO, "%s ~ %s", params, trailing);
+ server_info(s, "%s ~ %s", params, trailing);
else
- newlinef(s->channel, 0, FROM_INFO, "%s ~ are supported by this server", params);
+ server_info(s, "%s ~ are supported by this server", params);
server_set_005(s, m->params);
@@ 514,16 525,15 @@ irc_numeric_333(struct server *s, struct irc_message *m)
static int
irc_numeric_353(struct server *s, struct irc_message *m)
{
- /* 353 <nick> <type> <channel> 1*(<modes><nick>) */
+ /* <type> <channel> 1*(<modes><nick>) */
char *chan;
char *nick;
char *nicks;
- char *prfx;
- char *type;
+ char *prefix;
struct channel *c;
- if (!irc_message_param(m, &type))
+ if (!irc_message_param(m, &prefix))
failf(s, "RPL_NAMEREPLY: type is null");
if (!irc_message_param(m, &chan))
@@ 535,18 545,26 @@ irc_numeric_353(struct server *s, struct irc_message *m)
if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
failf(s, "RPL_NAMEREPLY: channel '%s' not found", chan);
- if (mode_chanmode_prefix(&(c->chanmodes), &(s->mode_cfg), *type) != MODE_ERR_NONE)
- failf(s, "RPL_NAMEREPLY: invalid channel flag: '%c'", *type);
+ if (*prefix != '@' && *prefix != '*' && *prefix != '=')
+ failf(s, "RPL_NAMEREPLY: invalid channel type: '%c'", *prefix);
- while ((prfx = nick = irc_strsep(&nicks))) {
+ if (*prefix == '@')
+ (void) mode_chanmode_set(&(c->chanmodes), &(s->mode_cfg), 's', 1);
+
+ if (*prefix == '*')
+ (void) mode_chanmode_set(&(c->chanmodes), &(s->mode_cfg), 'p', 1);
+
+ c->chanmodes.prefix = *prefix;
+
+ while ((prefix = nick = irc_strsep(&nicks))) {
struct mode m = MODE_EMPTY;
- while (mode_prfxmode_prefix(&m, &(s->mode_cfg), *nick) == MODE_ERR_NONE)
+ while (mode_prfxmode_set(&m, &(s->mode_cfg), *nick, 1) == MODE_ERR_NONE)
nick++;
if (*nick == 0)
- failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prfx);
+ failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prefix);
if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);
@@ 617,11 635,11 @@ irc_numeric_433(struct server *s, struct irc_message *m)
if (!irc_message_param(m, &nick))
failf(s, "ERR_NICKNAMEINUSE: nick is null");
- newlinef(s->channel, 0, FROM_ERROR, "Nick '%s' in use", nick);
+ server_error(s, "Nick '%s' in use", nick);
if (!strcmp(nick, s->nick)) {
server_nicks_next(s);
- newlinef(s->channel, 0, FROM_ERROR, "Trying again with '%s'", s->nick);
+ server_error(s, "Trying again with '%s'", s->nick);
sendf(s, "NICK %s", s->nick);
}
@@ 695,7 713,7 @@ recv_invite(struct server *s, struct irc_message *m)
failf(s, "INVITE: channel is null");
if (!strcmp(nick, s->nick)) {
- newlinef(s->channel, 0, FROM_INFO, "%s invited you to %s", m->from, chan);
+ server_info(s, "%s invited you to %s", m->from, chan);
return 0;
}
@@ 854,7 872,7 @@ recv_mode(struct server *s, struct irc_message *m)
struct channel *c;
if (!irc_message_param(m, &targ))
- failf(s, "NICK: new nick is null");
+ failf(s, "MODE: target nick is null");
if (!strcmp(targ, s->nick))
return recv_mode_usermodes(m, &(s->mode_cfg), s);
@@ 872,48 890,50 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
char *modestring;
char *modearg;
enum mode_err mode_err;
- enum mode_set mode_set;
struct mode *chanmodes = &(c->chanmodes);
struct user *user;
- // TODO: mode string segfaults if args out of order
-
if (!irc_message_param(m, &modestring)) {
newlinef(c, 0, FROM_ERROR, "MODE: modestring is null");
return 1;
}
do {
- mode_set = MODE_SET_INVALID;
+ int set = -1;
mode_err = MODE_ERR_NONE;
while ((flag = *modestring++)) {
+ if (flag == '-') {
+ set = 0;
+ continue;
+ }
+
if (flag == '+') {
- mode_set = MODE_SET_ON;
+ set = 1;
continue;
}
- if (flag == '-') {
- mode_set = MODE_SET_OFF;
+ if (set == -1) {
+ newlinef(c, 0, FROM_ERROR, "MODE: missing '+'/'-'");
continue;
}
modearg = NULL;
- switch (chanmode_type(cfg, mode_set, flag)) {
+ switch (mode_type(cfg, flag, set)) {
/* Doesn't consume an argument */
case MODE_FLAG_CHANMODE:
- mode_err = mode_chanmode_set(chanmodes, cfg, flag, mode_set);
+ mode_err = mode_chanmode_set(chanmodes, cfg, flag, set);
if (mode_err == MODE_ERR_NONE) {
newlinef(c, 0, FROM_INFO, "%s%s%s mode: %c%c",
(m->from ? m->from : ""),
(m->from ? " set " : ""),
c->name,
- (mode_set == MODE_SET_ON ? '+' : '-'),
+ (set ? '+' : '-'),
flag);
}
break;
@@ 926,14 946,14 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
continue;
}
- mode_err = mode_chanmode_set(chanmodes, cfg, flag, mode_set);
+ mode_err = mode_chanmode_set(chanmodes, cfg, flag, set);
if (mode_err == MODE_ERR_NONE) {
newlinef(c, 0, FROM_INFO, "%s%s%s mode: %c%c %s",
(m->from ? m->from : ""),
(m->from ? " set " : ""),
c->name,
- (mode_set == MODE_SET_ON ? '+' : '-'),
+ (set ? '+' : '-'),
flag,
modearg);
}
@@ 952,48 972,30 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
continue;
}
- mode_prfxmode_set(&(user->prfxmodes), cfg, flag, mode_set);
+ mode_prfxmode_set(&(user->prfxmodes), cfg, flag, set);
if (mode_err == MODE_ERR_NONE) {
newlinef(c, 0, FROM_INFO, "%s%suser %s mode: %c%c",
(m->from ? m->from : ""),
(m->from ? " set " : ""),
modearg,
- (mode_set == MODE_SET_ON ? '+' : '-'),
+ (set ? '+' : '-'),
flag);
}
break;
- case MODE_FLAG_INVALID_SET:
- mode_err = MODE_ERR_INVALID_SET;
- break;
-
case MODE_FLAG_INVALID_FLAG:
- mode_err = MODE_ERR_INVALID_FLAG;
+ newlinef(c, 0, FROM_ERROR, "MODE: invalid flag '%c'", flag);
break;
default:
newlinef(c, 0, FROM_ERROR, "MODE: unhandled error, flag '%c'", flag);
continue;
}
-
- switch (mode_err) {
-
- case MODE_ERR_INVALID_FLAG:
- newlinef(c, 0, FROM_ERROR, "MODE: invalid flag '%c'", flag);
- break;
-
- case MODE_ERR_INVALID_SET:
- newlinef(c, 0, FROM_ERROR, "MODE: missing '+'/'-'");
- break;
-
- default:
- break;
- }
}
} while (irc_message_param(m, &modestring));
- mode_str(&(c->chanmodes), &(c->chanmodes_str));
+ mode_str(&(c->chanmodes), &(c->chanmodes_str), MODE_STR_CHANMODE);
draw(DRAW_STATUS);
return 0;
@@ 1005,45 1007,46 @@ recv_mode_usermodes(struct irc_message *m, const struct mode_cfg *cfg, struct se
char flag;
char *modestring;
enum mode_err mode_err;
- enum mode_set mode_set;
struct mode *usermodes = &(s->usermodes);
if (!irc_message_param(m, &modestring))
failf(s, "MODE: modestring is null");
do {
- mode_set = MODE_SET_INVALID;
+ int set = -1;
while ((flag = *modestring++)) {
+ if (flag == '-') {
+ set = 0;
+ continue;
+ }
+
if (flag == '+') {
- mode_set = MODE_SET_ON;
+ set = 1;
continue;
}
- if (flag == '-') {
- mode_set = MODE_SET_OFF;
+ if (set == -1) {
+ server_error(s, "MODE: missing '+'/'-'");
continue;
}
- mode_err = mode_usermode_set(usermodes, cfg, flag, mode_set);
+ mode_err = mode_usermode_set(usermodes, cfg, flag, set);
if (mode_err == MODE_ERR_NONE)
- newlinef(s->channel, 0, FROM_INFO, "%s%smode: %c%c",
+ server_info(s, "%s%smode: %c%c",
(m->from ? m->from : ""),
(m->from ? " set " : ""),
- (mode_set == MODE_SET_ON ? '+' : '-'),
+ (set ? '+' : '-'),
flag);
- else if (mode_err == MODE_ERR_INVALID_SET)
- newlinef(s->channel, 0, FROM_ERROR, "MODE: missing '+'/'-'");
-
else if (mode_err == MODE_ERR_INVALID_FLAG)
- newlinef(s->channel, 0, FROM_ERROR, "MODE: invalid flag '%c'", flag);
+ server_error(s, "MODE: invalid flag '%c'", flag);
}
} while (irc_message_param(m, &modestring));
- mode_str(usermodes, &(s->mode_str));
+ mode_str(usermodes, &(s->mode_str), MODE_STR_USERMODE);
draw(DRAW_STATUS);
return 0;
@@ 1100,9 1103,6 @@ recv_notice(struct server *s, struct irc_message *m)
if (!irc_message_param(m, &message))
failf(s, "NOTICE: message is null");
- if (user_list_get(&(s->ignore), s->casemapping, m->from, 0))
- return 0;
-
if (IS_CTCP(message))
return ctcp_response(s, m->from, target, message);
@@ 1210,9 1210,6 @@ recv_privmsg(struct server *s, struct irc_message *m)
if (!irc_message_param(m, &message))
failf(s, "PRIVMSG: message is null");
- if (user_list_get(&(s->ignore), s->casemapping, m->from, 0))
- return 0;
-
if (IS_CTCP(message))
return ctcp_request(s, m->from, target, message);
@@ 1317,7 1314,23 @@ recv_topic(struct server *s, struct irc_message *m)
static int
recv_ircv3_cap(struct server *s, struct irc_message *m)
{
- return ircv3_recv_CAP(s, m);
+ int ret;
+
+ if ((ret = ircv3_recv_CAP(s, m)) && !s->registered)
+ io_dx(s->connection);
+
+ return ret;
+}
+
+static int
+recv_ircv3_authenticate(struct server *s, struct irc_message *m)
+{
+ int ret;
+
+ if ((ret = ircv3_recv_AUTHENTICATE(s, m)) && !s->registered)
+ io_dx(s->connection);
+
+ return ret;
}
static int
M src/handlers/irc_recv.gperf => src/handlers/irc_recv.gperf +20 -18
@@ 15,9 15,10 @@
X(privmsg) \
X(quit) \
X(topic) \
- X(ircv3_cap) \
X(ircv3_account) \
+ X(ircv3_authenticate) \
X(ircv3_away) \
+ X(ircv3_cap) \
X(ircv3_chghost)
#define X(cmd) static int recv_##cmd(struct server*, struct irc_message*);
@@ 44,21 45,22 @@ struct recv_handler
%define initializer-suffix ,(irc_recv_f)0
struct recv_handler;
%%
-ERROR, recv_error
-INVITE, recv_invite
-JOIN, recv_join
-KICK, recv_kick
-MODE, recv_mode
-NICK, recv_nick
-NOTICE, recv_notice
-PART, recv_part
-PING, recv_ping
-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
+ERROR, recv_error
+INVITE, recv_invite
+JOIN, recv_join
+KICK, recv_kick
+MODE, recv_mode
+NICK, recv_nick
+NOTICE, recv_notice
+PART, recv_part
+PING, recv_ping
+PONG, recv_pong
+PRIVMSG, recv_privmsg
+QUIT, recv_quit
+TOPIC, recv_topic
+ACCOUNT, recv_ircv3_account
+AUTHENTICATE, recv_ircv3_authenticate
+AWAY, recv_ircv3_away
+CAP, recv_ircv3_cap
+CHGHOST, recv_ircv3_chghost
%%
M src/handlers/ircv3.c => src/handlers/ircv3.c +366 -13
@@ 3,11 3,13 @@
#include "src/io.h"
#include "src/state.h"
+#include "mbedtls/base64.h"
+
#include <string.h>
#define failf(S, ...) \
do { server_error((S), __VA_ARGS__); \
- return 1; \
+ return -1; \
} while (0)
#define sendf(S, ...) \
@@ 31,6 33,28 @@ IRCV3_RECV_HANDLERS
static int ircv3_cap_req_count(struct ircv3_caps*);
static int ircv3_cap_req_send(struct ircv3_caps*, struct server*);
+static int ircv3_cap_end(struct server*);
+static int ircv3_sasl_init(struct server*);
+
+static int ircv3_recv_AUTHENTICATE_EXTERNAL(struct server*, struct irc_message*);
+static int ircv3_recv_AUTHENTICATE_PLAIN(struct server*, struct irc_message*);
+
+int
+ircv3_recv_AUTHENTICATE(struct server *s, struct irc_message *m)
+{
+ if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_NONE)
+ failf(s, "AUTHENTICATE: no SASL mechanism");
+
+ if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_EXTERNAL)
+ return (ircv3_recv_AUTHENTICATE_EXTERNAL(s, m));
+
+ if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_PLAIN)
+ return (ircv3_recv_AUTHENTICATE_PLAIN(s, m));
+
+ fatal("unknown SASL authentication mechanism");
+
+ return 0;
+}
int
ircv3_recv_CAP(struct server *s, struct irc_message *m)
@@ 53,6 77,202 @@ ircv3_recv_CAP(struct server *s, struct irc_message *m)
failf(s, "CAP: unrecognized subcommand '%s'", cmnd);
}
+int
+ircv3_numeric_900(struct server *s, struct irc_message *m)
+{
+ /* <nick>!<ident>@<host> <account> :You are now logged in as <user> */
+
+ char *account;
+ char *message;
+ char *nick;
+
+ if (!irc_message_param(m, &nick))
+ failf(s, "RPL_LOGGEDIN: missing nick");
+
+ if (!irc_message_param(m, &account))
+ failf(s, "RPL_LOGGEDIN: missing account");
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_info(s, "SASL success: %s", message);
+ else
+ server_info(s, "SASL success: you are logged in as %s", account);
+
+ return 0;
+}
+
+int
+ircv3_numeric_901(struct server *s, struct irc_message *m)
+{
+ /* <nick>!<ident>@<host> :You are now logged out */
+
+ char *message;
+ char *nick;
+
+ if (!irc_message_param(m, &nick))
+ failf(s, "RPL_LOGGEDOUT: missing nick");
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_info(s, "%s", message);
+ else
+ server_info(s, "You are now logged out");
+
+ return 0;
+}
+
+int
+ircv3_numeric_902(struct server *s, struct irc_message *m)
+{
+ /* :You must use a nick assigned to you */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_error(s, "%s", message);
+ else
+ server_error(s, "You must use a nick assigned to you");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;
+
+ if (!s->registered)
+ io_dx(s->connection);
+
+ return 0;
+}
+
+int
+ircv3_numeric_903(struct server *s, struct irc_message *m)
+{
+ /* :SASL authentication successful */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_info(s, "%s", message);
+ else
+ server_info(s, "SASL authentication successful");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_AUTHENTICATED;
+
+ return ircv3_cap_end(s);
+}
+
+int
+ircv3_numeric_904(struct server *s, struct irc_message *m)
+{
+ /* :SASL authentication failed */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_error(s, "%s", message);
+ else
+ server_error(s, "SASL authentication failed");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;
+
+ if (!s->registered)
+ io_dx(s->connection);
+
+ return 0;
+}
+
+int
+ircv3_numeric_905(struct server *s, struct irc_message *m)
+{
+ /* :SASL message too long */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_error(s, "%s", message);
+ else
+ server_error(s, "SASL message too long");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;
+
+ if (!s->registered)
+ io_dx(s->connection);
+
+ return 0;
+}
+
+int
+ircv3_numeric_906(struct server *s, struct irc_message *m)
+{
+ /* :SASL authentication aborted */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_error(s, "%s", message);
+ else
+ server_error(s, "SASL authentication aborted");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;
+
+ if (!s->registered)
+ io_dx(s->connection);
+
+ return 0;
+}
+
+int
+ircv3_numeric_907(struct server *s, struct irc_message *m)
+{
+ /* :You have already authenticated using SASL */
+
+ char *message;
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_error(s, "%s", message);
+ else
+ server_error(s, "You have already authenticated using SASL");
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;
+
+ if (!s->registered)
+ io_dx(s->connection);
+
+ return 0;
+}
+
+int
+ircv3_numeric_908(struct server *s, struct irc_message *m)
+{
+ /* <mechanisms> :are available SASL mechanisms */
+
+ char *mechanisms;
+ char *message;
+
+ if (!irc_message_param(m, &mechanisms))
+ failf(s, "RPL_SASLMECHS: missing mechanisms");
+
+ irc_message_param(m, &message);
+
+ if (message && *message)
+ server_info(s, "%s %s", mechanisms, message);
+ else
+ server_info(s, "%s are available SASL mechanisms", mechanisms);
+
+ return 0;
+}
+
static int
ircv3_recv_cap_LS(struct server *s, struct irc_message *m)
{
@@ 68,7 288,8 @@ ircv3_recv_cap_LS(struct server *s, struct irc_message *m)
* CAP <targ> LS [*] :[<cap_1> [...]]
*/
- char *cap;
+ char *cap_key;
+ char *cap_val;
char *caps;
char *multiline;
@@ 94,14 315,18 @@ ircv3_recv_cap_LS(struct server *s, struct irc_message *m)
return 0;
}
- while ((cap = irc_strsep(&(caps)))) {
+ while ((cap_key = irc_strsep(&(caps)))) {
struct ircv3_cap *c;
- if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap)))
+ if ((cap_val = strchr(cap_key, '=')))
+ *cap_val++ = 0;
+
+ if (!(c = ircv3_cap_get(&(s->ircv3_caps), cap_key)))
continue;
c->supported = 1;
+ c->val = (cap_val ? strdup(cap_val) : NULL);
if (c->req_auto)
c->req = 1;
@@ 217,17 442,18 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)
c->req = 0;
c->set = !unset;
- server_info(s, "capability change accepted: %s%s", (unset ? "-" : ""), cap);
+ server_info(s, "capability change accepted: %s%s%s%s",
+ (unset ? "-" : ""), cap, (c->val ? "=" : ""), (c->val ? c->val : ""));
+
+ if (!strcmp(cap, "sasl"))
+ ircv3_sasl_init(s);
} while ((cap = irc_strsep(&(caps))));
if (errors)
failf(s, "CAP ACK: parameter errors");
- if (!s->registered && !ircv3_cap_req_count(&(s->ircv3_caps)))
- sendf(s, "CAP END");
-
- return 0;
+ return ircv3_cap_end(s);
}
static int
@@ 258,10 484,7 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)
} while ((cap = irc_strsep(&(caps))));
- if (!s->registered && !ircv3_cap_req_count(&(s->ircv3_caps)))
- sendf(s, "CAP END");
-
- return 0;
+ return ircv3_cap_end(s);
}
static int
@@ 350,6 573,75 @@ ircv3_recv_cap_NEW(struct server *s, struct irc_message *m)
}
static int
+ircv3_recv_AUTHENTICATE_EXTERNAL(struct server *s, struct irc_message *m)
+{
+ /* C: AUTHENTICATE EXTERNAL
+ * S: AUTHENTICATE +
+ * C: AUTHENTICATE +
+ */
+
+ char *resp;
+
+ if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_MECH)
+ failf(s, "Invalid SASL state for mechanism EXTERNAL: %d", s->ircv3_sasl.state);
+
+ if (!irc_message_param(m, &resp))
+ failf(s, "Invalid SASL response for mechanism EXTERNAL: response is null");
+
+ if (strcmp(resp, "+"))
+ failf(s, "Invalid SASL response for mechanism EXTERNAL: '%s'", resp);
+
+ sendf(s, "AUTHENTICATE +");
+
+ return 0;
+}
+
+static int
+ircv3_recv_AUTHENTICATE_PLAIN(struct server *s, struct irc_message *m)
+{
+ /* C: AUTHENTICATE PLAIN
+ * S: AUTHENTICATE +
+ * C: AUTHENTICATE base64(<authzid><null><authcid><null><passwd>)
+ */
+
+ /* (((4 * 300 / 3) + 3) & ~3) */
+ char *resp;
+ unsigned char resp_dec[300];
+ unsigned char resp_enc[400];
+ size_t len;
+
+ if (!s->ircv3_sasl.user || !*s->ircv3_sasl.user)
+ failf(s, "SASL mechanism PLAIN requires a username");
+
+ if (!s->ircv3_sasl.pass || !*s->ircv3_sasl.pass)
+ failf(s, "SASL mechanism PLAIN requires a password");
+
+ if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_MECH)
+ failf(s, "Invalid SASL state for mechanism PLAIN: %d", s->ircv3_sasl.state);
+
+ if (!irc_message_param(m, &resp))
+ failf(s, "Invalid SASL response for mechanism PLAIN: response is null");
+
+ if (strcmp(resp, "+"))
+ failf(s, "Invalid SASL response for mechanism PLAIN: '%s'", resp);
+
+ len = snprintf((char *)resp_dec, sizeof(resp_dec), "%s%c%s%c%s",
+ s->ircv3_sasl.user, 0,
+ s->ircv3_sasl.user, 0,
+ s->ircv3_sasl.pass);
+
+ if (len >= sizeof(resp_dec))
+ failf(s, "SASL decoded auth message too long");
+
+ if (mbedtls_base64_encode(resp_enc, sizeof(resp_enc), &len, resp_dec, len))
+ failf(s, "SASL encoded auth message too long");
+
+ sendf(s, "AUTHENTICATE %s", resp_enc);
+
+ return 0;
+}
+
+static int
ircv3_cap_req_count(struct ircv3_caps *caps)
{
#define X(CAP, VAR, ATTRS) + !!caps->VAR.req
@@ 382,3 674,64 @@ ircv3_cap_req_send(struct ircv3_caps *caps, struct server *s)
return 0;
}
+
+static int
+ircv3_cap_end(struct server *s)
+{
+ /* Previously registered or doesn't support IRCv3 */
+ if (s->registered)
+ return 0;
+
+ /* IRCv3 CAP negotiation in progress */
+ if (ircv3_cap_req_count(&(s->ircv3_caps)))
+ return 0;
+
+ /* IRCv3 SASL authentication in progress */
+ if (s->ircv3_sasl.state != IRCV3_SASL_STATE_NONE
+ && s->ircv3_sasl.state != IRCV3_SASL_STATE_AUTHENTICATED)
+ return 0;
+
+ sendf(s, "CAP END");
+
+ return 0;
+}
+
+static int
+ircv3_sasl_init(struct server *s)
+{
+ if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_NONE)
+ return 0;
+
+ switch (s->ircv3_sasl.state) {
+
+ /* Start authentication process */
+ case IRCV3_SASL_STATE_NONE:
+ break;
+
+ /* Authentication in progress */
+ case IRCV3_SASL_STATE_REQ_MECH:
+ return 0;
+
+ /* Previously authenticated */
+ case IRCV3_SASL_STATE_AUTHENTICATED:
+ return 0;
+
+ default:
+ fatal("unknown SASL state");
+ }
+
+ switch (s->ircv3_sasl.mech) {
+ case IRCV3_SASL_MECH_EXTERNAL:
+ sendf(s, "AUTHENTICATE EXTERNAL");
+ break;
+ case IRCV3_SASL_MECH_PLAIN:
+ sendf(s, "AUTHENTICATE PLAIN");
+ break;
+ default:
+ fatal("unknown SASL authentication mechanism");
+ }
+
+ s->ircv3_sasl.state = IRCV3_SASL_STATE_REQ_MECH;
+
+ return 0;
+}
M src/handlers/ircv3.h => src/handlers/ircv3.h +11 -0
@@ 4,6 4,17 @@
#include "src/components/server.h"
#include "src/utils/utils.h"
+int ircv3_recv_AUTHENTICATE(struct server*, struct irc_message*);
int ircv3_recv_CAP(struct server*, struct irc_message*);
+int ircv3_numeric_900(struct server*, struct irc_message*);
+int ircv3_numeric_901(struct server*, struct irc_message*);
+int ircv3_numeric_902(struct server*, struct irc_message*);
+int ircv3_numeric_903(struct server*, struct irc_message*);
+int ircv3_numeric_904(struct server*, struct irc_message*);
+int ircv3_numeric_905(struct server*, struct irc_message*);
+int ircv3_numeric_906(struct server*, struct irc_message*);
+int ircv3_numeric_907(struct server*, struct irc_message*);
+int ircv3_numeric_908(struct server*, struct irc_message*);
+
#endif
M src/io.c => src/io.c +147 -69
@@ 105,6 105,9 @@ struct connection
const void *obj;
const char *host;
const char *port;
+ const char *tls_ca_file;
+ const char *tls_ca_path;
+ const char *tls_cert;
enum io_state {
IO_ST_INVALID,
IO_ST_DXED, /* Socket disconnected, passive */
@@ 114,9 117,14 @@ struct connection
IO_ST_PING, /* Socket connected, network state in question */
} st_cur, /* current thread state */
st_new; /* new thread state */
+ mbedtls_ctr_drbg_context tls_ctr_drbg;
+ mbedtls_entropy_context tls_entropy;
mbedtls_net_context net_ctx;
- mbedtls_ssl_config tls_conf;
+ mbedtls_pk_context tls_pk_ctx;
+ mbedtls_ssl_config tls_conf;
mbedtls_ssl_context tls_ctx;
+ mbedtls_x509_crt tls_x509_crt_ca;
+ mbedtls_x509_crt tls_x509_crt_client;
pthread_mutex_t mtx;
pthread_t tid;
uint32_t flags;
@@ 138,9 146,6 @@ static void io_tty_winsize(void);
static void* io_thread(void*);
static int io_running;
-static mbedtls_ctr_drbg_context tls_ctr_drbg;
-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 volatile sig_atomic_t flag_sigwinch_cb; /* sigwinch callback */
@@ 153,10 158,8 @@ static void io_net_close(int);
static const char* io_tls_err(int);
static int io_tls_establish(struct connection*);
static int io_tls_x509_vrfy(struct connection*);
-static void io_tls_init(void);
-static void io_tls_term(void);
-const char *ca_cert_paths[] = {
+const char *default_ca_certs[] = {
"/etc/ssl/ca-bundle.pem",
"/etc/ssl/cert.pem",
"/etc/ssl/certs/ca-certificates.crt",
@@ 166,7 169,14 @@ const char *ca_cert_paths[] = {
};
struct connection*
-connection(const void *obj, const char *host, const char *port, uint32_t flags)
+connection(
+ const void *obj,
+ const char *host,
+ const char *port,
+ const char *tls_ca_file,
+ const char *tls_ca_path,
+ const char *tls_cert,
+ uint32_t flags)
{
struct connection *cx;
@@ 177,6 187,9 @@ connection(const void *obj, const char *host, const char *port, uint32_t flags)
cx->flags = flags;
cx->host = strdup(host);
cx->port = strdup(port);
+ cx->tls_ca_file = (tls_ca_file ? strdup(tls_ca_file) : NULL);
+ cx->tls_ca_path = (tls_ca_path ? strdup(tls_ca_path) : NULL);
+ cx->tls_cert = (tls_cert ? strdup(tls_cert) : NULL);
cx->st_cur = IO_ST_DXED;
cx->st_new = IO_ST_INVALID;
PT_CF(pthread_mutex_init(&(cx->mtx), NULL));
@@ 190,6 203,9 @@ connection_free(struct connection *cx)
PT_CF(pthread_mutex_destroy(&(cx->mtx)));
free((void*)cx->host);
free((void*)cx->port);
+ free((void*)cx->tls_ca_file);
+ free((void*)cx->tls_ca_path);
+ free((void*)cx->tls_cert);
free(cx);
}
@@ 309,7 325,6 @@ io_init(void)
{
io_sig_init();
io_tty_init();
- io_tls_init();
}
void
@@ 411,8 426,17 @@ io_state_cxed(struct connection *cx)
{
int ret;
- while ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_MIN))) > 0)
- continue;
+ do {
+ enum io_state st;
+
+ PT_LK(&(cx->mtx));
+ st = cx->st_new;
+ PT_UL(&(cx->mtx));
+
+ if (st != IO_ST_INVALID)
+ return st;
+
+ } while ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_MIN))) > 0);
if (ret == MBEDTLS_ERR_SSL_TIMEOUT)
return IO_ST_PING;
@@ 421,22 445,28 @@ io_state_cxed(struct connection *cx)
case MBEDTLS_ERR_SSL_WANT_READ:
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
- io_info(cx, "connection closed gracefully");
+ io_info(cx, "Connection closed gracefully");
break;
case MBEDTLS_ERR_NET_CONN_RESET:
case 0:
- io_error(cx, "connection reset by peer");
+ io_error(cx, "Connection reset by peer");
break;
default:
- io_error(cx, "connection error");
+ io_error(cx, "Connection error");
break;
}
mbedtls_net_free(&(cx->net_ctx));
if (cx->flags & IO_TLS_ENABLED) {
+ mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
+ mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
+
}
return IO_ST_CXNG;
@@ 460,22 490,27 @@ io_state_ping(struct connection *cx)
case MBEDTLS_ERR_SSL_WANT_READ:
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
- io_info(cx, "connection closed gracefully");
+ io_info(cx, "Connection closed gracefully");
break;
case MBEDTLS_ERR_NET_CONN_RESET:
case 0:
- io_error(cx, "connection reset by peer");
+ io_error(cx, "Connection reset by peer");
break;
default:
- io_error(cx, "connection error");
+ io_error(cx, "Connection error");
break;
}
mbedtls_net_free(&(cx->net_ctx));
if (cx->flags & IO_TLS_ENABLED) {
+ mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
+ mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
}
return IO_ST_CXNG;
@@ 776,12 811,19 @@ io_strerror(char *buf, size_t buflen)
static int
io_tls_establish(struct connection *cx)
{
+ const unsigned char pers[] = "rirc-drbg-seed";
+
int ret;
io_info(cx, " .. Establishing TLS connection");
+ mbedtls_ctr_drbg_init(&(cx->tls_ctr_drbg));
+ mbedtls_entropy_init(&(cx->tls_entropy));
+ mbedtls_pk_init(&(cx->tls_pk_ctx));
mbedtls_ssl_init(&(cx->tls_ctx));
mbedtls_ssl_config_init(&(cx->tls_conf));
+ mbedtls_x509_crt_init(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_init(&(cx->tls_x509_crt_client));
if ((ret = mbedtls_ssl_config_defaults(
&(cx->tls_conf),
@@ 802,12 844,91 @@ io_tls_establish(struct connection *cx)
MBEDTLS_SSL_MAJOR_VERSION_3,
MBEDTLS_SSL_MINOR_VERSION_3);
- mbedtls_ssl_conf_rng(&(cx->tls_conf), mbedtls_ctr_drbg_random, &tls_ctr_drbg);
+ if ((ret = mbedtls_ctr_drbg_seed(
+ &(cx->tls_ctr_drbg),
+ mbedtls_entropy_func,
+ &(cx->tls_entropy),
+ pers,
+ sizeof(pers)))) {
+ io_error(cx, " .. %s ", io_tls_err(ret));
+ goto err;
+ }
+
+ ret = -1;
+
+ if (ret < 0 && cx->tls_ca_file) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), cx->tls_ca_file)) < 0) {
+ io_error(cx, " .. Failed to load CA cert file: '%s': %s", cx->tls_ca_file, io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ if (ret < 0 && cx->tls_ca_path) {
+ if ((ret = mbedtls_x509_crt_parse_path(&(cx->tls_x509_crt_ca), cx->tls_ca_path)) < 0) {
+ io_error(cx, " .. Failed to load CA cert path: '%s': %s", cx->tls_ca_path, io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ if (ret < 0 && default_ca_file && *default_ca_file) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), default_ca_file)) < 0) {
+ io_error(cx, " .. Failed to load CA cert file: '%s': %s", default_ca_file, io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ if (ret < 0 && default_ca_path && *default_ca_path) {
+ if ((ret = mbedtls_x509_crt_parse_path(&(cx->tls_x509_crt_ca), default_ca_path)) < 0) {
+ io_error(cx, " .. Failed to load CA cert path: '%s': %s", default_ca_path, io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ if (ret < 0) {
+
+ size_t i;
+
+ for (i = 0; i < ARR_LEN(default_ca_certs); i++) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), default_ca_certs[i])) >= 0)
+ break;
+ }
+
+ if (i == ARR_LEN(default_ca_certs)) {
+ io_error(cx, " .. Failed to load default CA certs: %s", io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ if (cx->tls_cert) {
+
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_client), cx->tls_cert)) < 0) {
+ io_error(cx, " .. Failed to load client cert: '%s': %s", cx->tls_cert, io_tls_err(ret));
+ goto err;
+ }
+
+ if ((ret = mbedtls_pk_parse_keyfile(
+ &(cx->tls_pk_ctx),
+ cx->tls_cert,
+ NULL,
+ mbedtls_ctr_drbg_random,
+ &(cx->tls_ctr_drbg))))
+ {
+ io_error(cx, " .. Failed to load client cert key: '%s': %s", cx->tls_cert, io_tls_err(ret));
+ goto err;
+ }
+
+ if ((ret = mbedtls_ssl_conf_own_cert(&(cx->tls_conf), &(cx->tls_x509_crt_client), &(cx->tls_pk_ctx)))) {
+ io_error(cx, " .. Failed to configure client cert: '%s': %s", cx->tls_cert, io_tls_err(ret));
+ goto err;
+ }
+ }
+
+ mbedtls_ssl_conf_rng(&(cx->tls_conf), mbedtls_ctr_drbg_random, &(cx->tls_ctr_drbg));
if (cx->flags & IO_TLS_VRFY_DISABLED) {
mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_NONE);
} else {
- mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &tls_x509_crt, NULL);
+ mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &(cx->tls_x509_crt_ca), NULL);
if (cx->flags & IO_TLS_VRFY_OPTIONAL)
mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_OPTIONAL);
@@ 858,8 979,8 @@ io_tls_establish(struct connection *cx)
}
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)));
+ 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;
@@ 867,8 988,13 @@ err:
io_error(cx, " .. TLS connection failure");
+ mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
+ mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
mbedtls_net_free(&(cx->net_ctx));
return -1;
@@ 914,51 1040,3 @@ io_tls_err(int err)
return "Unknown error";
}
-
-static void
-io_tls_init(void)
-{
- const unsigned char pers[] = "rirc-drbg-seed";
- int ret;
-
- mbedtls_ctr_drbg_init(&tls_ctr_drbg);
- mbedtls_entropy_init(&tls_entropy);
- mbedtls_x509_crt_init(&tls_x509_crt);
-
- if (atexit(io_tls_term))
- fatal("atexit");
-
- if ((ret = mbedtls_ctr_drbg_seed(
- &tls_ctr_drbg,
- mbedtls_entropy_func,
- &tls_entropy,
- pers,
- sizeof(pers)))) {
- fatal("mbedtls_ctr_drbg_seed: %s", io_tls_err(ret));
- }
-
- if (ca_cert_path && *ca_cert_path) {
-
- if ((ret = mbedtls_x509_crt_parse_file(&tls_x509_crt, ca_cert_path)) < 0)
- fatal("mbedtls_x509_crt_parse_file: '%s': %s", ca_cert_path, io_tls_err(ret));
-
- } else {
-
- for (size_t i = 0; i < ARR_LEN(ca_cert_paths); i++) {
- if ((ret = mbedtls_x509_crt_parse_file(&tls_x509_crt, ca_cert_paths[i])) >= 0)
- return;
- }
-
- fatal("Failed to load ca cert: %s", io_tls_err(ret));
- }
-}
-
-static void
-io_tls_term(void)
-{
- /* Exit handler, must return normally */
-
- mbedtls_ctr_drbg_free(&tls_ctr_drbg);
- mbedtls_entropy_free(&tls_entropy);
- mbedtls_x509_crt_free(&tls_x509_crt);
-}
M src/io.h => src/io.h +3 -0
@@ 89,6 89,9 @@ struct connection* connection(
const void*, /* callback object */
const char*, /* host */
const char*, /* port */
+ const char*, /* tls_ca_file */
+ const char*, /* tls_ca_path */
+ const char*, /* tls_cert */
uint32_t); /* flags */
void connection_free(struct connection*);
M src/rirc.c => src/rirc.c +156 -64
@@ 11,6 11,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#include <unistd.h>
#define MAX_CLI_SERVERS 64
@@ 25,10 26,16 @@ static const char* rirc_opt_str(char);
static const char* rirc_pw_name(void);
static int rirc_parse_args(int, char**);
+#ifdef CA_CERT_FILE
+const char *default_ca_file = CA_CERT_FILE;
+#else
+const char *default_ca_file;
+#endif
+
#ifdef CA_CERT_PATH
-const char *ca_cert_path = CA_CERT_PATH;
+const char *default_ca_path = CA_CERT_PATH;
#else
-const char *ca_cert_path;
+const char *default_ca_path;
#endif
#ifdef DEFAULT_NICKS
@@ 59,26 66,31 @@ static const char *const rirc_help =
"\nrirc v"VERSION" ~ Richard C. Robbins <mail@rcr.io>"
"\n"
"\nUsage:"
-"\n rirc [-hv] [-s server [...]]"
+"\n rirc [-hv] [-s host [options] ...]"
"\n"
"\nInfo:"
"\n -h, --help Print help message and exit"
"\n -v, --version Print rirc version and exit"
"\n"
-"\nServer options:"
-"\n -s, --server=SERVER Connect to SERVER"
-"\n -p, --port=PORT Connect to SERVER using PORT"
-"\n -w, --pass=PASS Connect to SERVER using PASS"
-"\n -u, --username=USERNAME Connect to SERVER using USERNAME"
-"\n -r, --realname=REALNAME Connect to SERVER using REALNAME"
-"\n -n, --nicks=NICKS Comma separated list of nicks to use for SERVER"
-"\n -c, --chans=CHANNELS Comma separated list of channels to join for SERVER"
-"\n"
-"\nServer connection options:"
-"\n --ipv4 Connect to server using only ipv4 addresses"
-"\n --ipv6 Connect to server using only ipv6 addresses"
-"\n --tls-disable Set server TLS disabled"
-"\n --tls-verify=<mode> Set server TLS peer certificate verification mode"
+"\nOptions:"
+"\n -s, --server=HOST Set connection hostname"
+"\n -p, --port=PORT Set connection port"
+"\n -w, --pass=PASS Set IRC password"
+"\n -u, --username=USERNAME Set IRC username"
+"\n -r, --realname=REALNAME Set IRC realname"
+"\n -m, --mode=MODE Set IRC user modes"
+"\n -n, --nicks=NICKS Set comma separated list of nicks to use"
+"\n -c, --chans=CHANNELS Set comma separated list of channels to join"
+"\n --tls-cert=PATH Set TLS client certificate file path"
+"\n --tls-ca-file=PATH Set TLS peer certificate file path"
+"\n --tls-ca-path=PATH Set TLS peer certificate directory path"
+"\n --tls-verify=MODE Set TLS peer certificate verification mode"
+"\n --tls-disable Set TLS disabled"
+"\n --sasl=MECHANISM Authenticate with SASL mechanism"
+"\n --sasl-user=USER Authenticate with SASL username"
+"\n --sasl-pass=PASS Authenticate with SASL password"
+"\n --ipv4 Use IPv4 addresses only"
+"\n --ipv6 Use IPv6 addresses only"
"\n";
static const char *const rirc_version =
@@ 95,14 107,21 @@ rirc_opt_str(char c)
case 's': return "-s/--server";
case 'p': return "-p/--port";
case 'w': return "-w/--pass";
- case 'n': return "-n/--nicks";
- case 'c': return "-c/--chans";
case 'u': return "-u/--username";
case 'r': return "-r/--realname";
- case '4': return "--ipv4";
- case '6': return "--ipv6";
- case 'x': return "--tls-disable";
- case 'y': return "--tls-verify";
+ case 'm': return "-m/--mode";
+ case 'n': return "-n/--nicks";
+ case 'c': return "-c/--chans";
+ case '0': return "--tls-cert";
+ case '1': return "--tls-ca-file";
+ case '2': return "--tls-ca-path";
+ case '3': return "--tls-verify";
+ case '4': return "--tls-disable";
+ case '5': return "--sasl";
+ case '6': return "--sasl-user";
+ case '7': return "--sasl-pass";
+ case '8': return "--ipv4";
+ case '9': return "--ipv6";
default:
fatal("unknown option flag '%c'", c);
}
@@ 136,8 155,15 @@ rirc_parse_args(int argc, char **argv)
const char *pass;
const char *username;
const char *realname;
+ const char *mode;
const char *nicks;
const char *chans;
+ const char *tls_ca_file;
+ const char *tls_ca_path;
+ const char *tls_cert;
+ const char *sasl;
+ const char *sasl_user;
+ const char *sasl_pass;
int ipv;
int tls;
int tls_vrfy;
@@ 150,20 176,27 @@ rirc_parse_args(int argc, char **argv)
{"pass", required_argument, 0, 'w'},
{"username", required_argument, 0, 'u'},
{"realname", required_argument, 0, 'r'},
+ {"mode", required_argument, 0, 'm'},
{"nicks", required_argument, 0, 'n'},
{"chans", required_argument, 0, 'c'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
- {"ipv4", no_argument, 0, '4'},
- {"ipv6", no_argument, 0, '6'},
- {"tls-disable", no_argument, 0, 'x'},
- {"tls-verify", required_argument, 0, 'y'},
+ {"tls-cert", required_argument, 0, '0'},
+ {"tls-ca-file", required_argument, 0, '1'},
+ {"tls-ca-path", required_argument, 0, '2'},
+ {"tls-verify", required_argument, 0, '3'},
+ {"tls-disable", no_argument, 0, '4'},
+ {"sasl", required_argument, 0, '5'},
+ {"sasl-user", required_argument, 0, '6'},
+ {"sasl-pass", required_argument, 0, '7'},
+ {"ipv4", no_argument, 0, '8'},
+ {"ipv6", no_argument, 0, '9'},
{0, 0, 0, 0}
};
opterr = 0;
- while (0 < (opt_c = getopt_long(argc, argv, ":s:p:w:r:u:n:c:hv", long_opts, &opt_i))) {
+ while (0 < (opt_c = getopt_long(argc, argv, ":s:p:w:u:r:m:n:c:hv", long_opts, &opt_i))) {
switch (opt_c) {
@@ 179,16 212,23 @@ rirc_parse_args(int argc, char **argv)
return -1;
}
- cli_servers[n_servers - 1].host = optarg;
- cli_servers[n_servers - 1].port = NULL;
- cli_servers[n_servers - 1].pass = NULL;
- cli_servers[n_servers - 1].username = default_username;
- cli_servers[n_servers - 1].realname = default_realname;
- cli_servers[n_servers - 1].nicks = default_nicks;
- cli_servers[n_servers - 1].chans = NULL;
- cli_servers[n_servers - 1].ipv = IO_IPV_UNSPEC;
- cli_servers[n_servers - 1].tls = IO_TLS_ENABLED;
- cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
+ cli_servers[n_servers - 1].host = optarg;
+ cli_servers[n_servers - 1].port = NULL;
+ cli_servers[n_servers - 1].pass = NULL;
+ cli_servers[n_servers - 1].username = default_username;
+ cli_servers[n_servers - 1].realname = default_realname;
+ cli_servers[n_servers - 1].mode = NULL;
+ cli_servers[n_servers - 1].nicks = default_nicks;
+ cli_servers[n_servers - 1].chans = NULL;
+ cli_servers[n_servers - 1].tls_ca_file = NULL;
+ cli_servers[n_servers - 1].tls_ca_path = NULL;
+ cli_servers[n_servers - 1].tls_cert = NULL;
+ cli_servers[n_servers - 1].sasl = NULL;
+ cli_servers[n_servers - 1].sasl_user = NULL;
+ cli_servers[n_servers - 1].sasl_pass = NULL;
+ cli_servers[n_servers - 1].ipv = IO_IPV_UNSPEC;
+ cli_servers[n_servers - 1].tls = IO_TLS_ENABLED;
+ cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
break;
#define CHECK_SERVER_OPTARG(OPT_C, REQ) \
@@ 201,68 241,111 @@ rirc_parse_args(int argc, char **argv)
return -1; \
}
- case 'p': /* Connect using port */
+ case 'p': /* Set connection port */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].port = optarg;
break;
- case 'w': /* Connect using port */
+ case 'w': /* Set IRC password */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].pass = optarg;
break;
- case 'u': /* Connect using username */
+ case 'u': /* Set IRC username */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].username = optarg;
break;
- case 'r': /* Connect using realname */
+ case 'r': /* Set IRC realname */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].realname = optarg;
break;
- case 'n': /* Comma separated list of nicks to use */
+ case 'm': /* Set IRC user modes */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].mode = optarg;
+ break;
+
+ case 'n': /* Set comma separated list of nicks to use */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].nicks = optarg;
break;
- case 'c': /* Comma separated list of channels to join */
+ case 'c': /* Set comma separated list of channels to join */
CHECK_SERVER_OPTARG(opt_c, 1);
cli_servers[n_servers - 1].chans = optarg;
break;
- case '4': /* Connect using ipv4 only */
- CHECK_SERVER_OPTARG(opt_c, 0);
- cli_servers[n_servers -1].ipv = IO_IPV_4;
+ case '0': /* Set TLS client certificate file path */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].tls_cert = optarg;
break;
- case '6': /* Connect using ipv6 only */
- CHECK_SERVER_OPTARG(opt_c, 0);
- cli_servers[n_servers -1].ipv = IO_IPV_6;
+ case '1': /* Set TLS peer certificate file path */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].tls_ca_file = optarg;
break;
- case 'x': /* Set server TLS disabled */
- CHECK_SERVER_OPTARG(opt_c, 0);
- cli_servers[n_servers -1].tls = IO_TLS_DISABLED;
+ case '2': /* Set TLS peer certificate directory path */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].tls_ca_path = optarg;
break;
- case 'y': /* Set server TLS peer certificate verification mode */
+ case '3': /* Set TLS peer certificate verification mode */
CHECK_SERVER_OPTARG(opt_c, 1);
- if (!strcmp(optarg, "0") || !strcmp(optarg, "disabled")) {
+ if (!strcmp(optarg, "0") || !strcasecmp(optarg, "DISABLED")) {
cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_DISABLED;
break;
}
- if (!strcmp(optarg, "1") || !strcmp(optarg, "optional")) {
+ if (!strcmp(optarg, "1") || !strcasecmp(optarg, "OPTIONAL")) {
cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_OPTIONAL;
break;
}
- if (!strcmp(optarg, "2") || !strcmp(optarg, "required")) {
+ if (!strcmp(optarg, "2") || !strcasecmp(optarg, "REQUIRED")) {
cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
break;
}
arg_error("invalid option for '--tls-verify' '%s'", optarg);
return -1;
+ case '4': /* Set TLS disabled */
+ CHECK_SERVER_OPTARG(opt_c, 0);
+ cli_servers[n_servers -1].tls = IO_TLS_DISABLED;
+ break;
+
+ case '5': /* Authenticate with SASL mechanism */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ if (!strcasecmp(optarg, "EXTERNAL")) {
+ cli_servers[n_servers - 1].sasl = optarg;
+ break;
+ }
+ if (!strcasecmp(optarg, "PLAIN")) {
+ cli_servers[n_servers - 1].sasl = optarg;
+ break;
+ }
+ arg_error("invalid option for '--sasl' '%s'", optarg);
+ return -1;
+
+ case '6': /* Authenticate with SASL user */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].sasl_user = optarg;
+ break;
+
+ case '7': /* Authenticate with SASL pass */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].sasl_pass = optarg;
+ break;
+
+ case '8': /* Use IPv4 only */
+ CHECK_SERVER_OPTARG(opt_c, 0);
+ cli_servers[n_servers -1].ipv = IO_IPV_4;
+ break;
+
+ case '9': /* Use IPv6 only */
+ CHECK_SERVER_OPTARG(opt_c, 0);
+ cli_servers[n_servers -1].ipv = IO_IPV_6;
+ break;
+
#undef CHECK_SERVER_OPTARG
case 'h':
@@ 294,11 377,6 @@ rirc_parse_args(int argc, char **argv)
for (size_t i = 0; i < n_servers; i++) {
- uint32_t flags =
- cli_servers[i].ipv |
- cli_servers[i].tls |
- cli_servers[i].tls_vrfy;
-
if (cli_servers[i].port == NULL)
cli_servers[i].port = (cli_servers[i].tls == IO_TLS_ENABLED) ? "6697" : "6667";
@@ 307,14 385,20 @@ rirc_parse_args(int argc, char **argv)
cli_servers[i].port,
cli_servers[i].pass,
cli_servers[i].username,
- cli_servers[i].realname
+ cli_servers[i].realname,
+ cli_servers[i].mode
);
cli_servers[i].s->connection = connection(
cli_servers[i].s,
cli_servers[i].host,
cli_servers[i].port,
- flags);
+ cli_servers[i].tls_ca_file,
+ cli_servers[i].tls_ca_path,
+ cli_servers[i].tls_cert,
+ (cli_servers[i].ipv |
+ cli_servers[i].tls |
+ cli_servers[i].tls_vrfy));
if (server_list_add(state_server_list(), cli_servers[i].s)) {
arg_error("duplicate server: %s:%s", cli_servers[i].host, cli_servers[i].port);
@@ 331,6 415,14 @@ rirc_parse_args(int argc, char **argv)
return -1;
}
+ if (cli_servers[i].sasl) {
+ server_set_sasl(
+ cli_servers[i].s,
+ cli_servers[i].sasl,
+ cli_servers[i].sasl_user,
+ cli_servers[i].sasl_pass);
+ }
+
channel_set_current(cli_servers[i].s->channel);
}
M src/rirc.h => src/rirc.h +3 -2
@@ 3,8 3,9 @@
/* Default config values obtained at runtime */
-extern const char *ca_cert_path;
-extern const char *default_nick_set;
+extern const char *default_ca_file;
+extern const char *default_ca_path;
+extern const char *default_nicks;
extern const char *default_username;
extern const char *default_realname;
extern const char *runtime_name;
M src/state.c => src/state.c +274 -51
@@ 10,15 10,27 @@
#include "src/utils/utils.h"
#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.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)
+#define COMMAND_HANDLERS \
+ X(clear) \
+ X(close) \
+ X(connect) \
+ X(disconnect) \
+ X(quit)
+
+#define X(CMD) \
+static void command_##CMD(struct channel*, char*);
+COMMAND_HANDLERS
+#undef X
+
static void _newline(struct channel*, enum buffer_line_type, const char*, const char*, va_list);
static int state_input_linef(struct channel*);
@@ 198,8 210,6 @@ _newline(struct channel *c, enum buffer_line_type type, const char *from, const
}
}
- // TODO: preformat the time string here
-
buffer_newline(
&(c->buffer),
type,
@@ 587,78 597,289 @@ state_complete(char *str, uint16_t len, uint16_t max, int first)
}
static void
-command(struct channel *c, char *buf)
+command(struct channel *c, char *args)
{
const char *arg;
- const char *cmd;
- int err;
- if (!(cmd = irc_strsep(&buf)))
+ if (!(arg = irc_strsep(&args)))
return;
- if (!strcasecmp(cmd, "clear")) {
- if ((arg = irc_strsep(&buf))) {
- action(action_error, "clear: Unknown arg '%s'", arg);
- return;
- }
- state_channel_clear(0);
+ if (!strcasecmp(arg, "clear")) {
+ command_clear(c, args);
return;
}
- if (!strcasecmp(cmd, "close")) {
- if ((arg = irc_strsep(&buf))) {
- action(action_error, "close: Unknown arg '%s'", arg);
- return;
- }
- state_channel_close(0);
+ if (!strcasecmp(arg, "close")) {
+ command_close(c, args);
+ return;
+ }
+
+ if (!strcasecmp(arg, "connect")) {
+ command_connect(c, args);
+ return;
+ }
+
+ if (!strcasecmp(arg, "disconnect")) {
+ command_disconnect(c, args);
+ return;
+ }
+
+ if (!strcasecmp(arg, "quit")) {
+ command_quit(c, args);
+ return;
+ }
+
+ action(action_error, "Unknown command '%s'", arg);
+}
+
+static void
+command_clear(struct channel *c, char *args)
+{
+ UNUSED(c);
+
+ char *arg;
+
+ if ((arg = irc_strsep(&args))) {
+ action(action_error, "clear: Unknown arg '%s'", arg);
+ return;
+ }
+
+ state_channel_clear(0);
+}
+
+static void
+command_close(struct channel *c, char *args)
+{
+ UNUSED(c);
+
+ char *arg;
+
+ if ((arg = irc_strsep(&args))) {
+ action(action_error, "close: Unknown arg '%s'", arg);
return;
}
- if (!strcasecmp(cmd, "connect")) {
+ state_channel_close(0);
+}
+
+static void
+command_connect(struct channel *c, char *args)
+{
+ /* :connect [hostname [options]] */
+
+ char *arg;
+
+ if (!(arg = irc_strsep(&args))) {
+
+ int err;
+
if (!c->server) {
action(action_error, "connect: This is not a server");
- return;
+ } else if ((err = io_cx(c->server->connection))) {
+ action(action_error, "connect: %s", io_err(err));
}
- if ((arg = irc_strsep(&buf))) {
- action(action_error, "connect: Unknown arg '%s'", arg);
- return;
+ } else {
+
+ int ret;
+ struct server *s;
+
+ const char *host = arg;
+ const char *port = NULL;
+ const char *pass = NULL;
+ const char *username = default_username;
+ const char *realname = default_realname;
+ const char *mode = NULL;
+ const char *nicks = default_nicks;
+ const char *chans = NULL;
+ const char *tls_ca_file = NULL;
+ const char *tls_ca_path = NULL;
+ const char *tls_cert = NULL;
+ const char *sasl = NULL;
+ const char *sasl_user = NULL;
+ const char *sasl_pass = NULL;
+ int ipv = IO_IPV_UNSPEC;
+ int tls = IO_TLS_ENABLED;
+ int tls_vrfy = IO_TLS_VRFY_REQUIRED;
+
+ while ((arg = irc_strsep(&args))) {
+
+ if (*arg != '-') {
+ action(action_error, ":connect [hostname [options]]");
+ return;
+ } else if (!strcmp(arg, "-p") || !strcmp(arg, "--port")) {
+ if (!(port = irc_strsep(&args))) {
+ action(action_error, "connect: '-p/--port' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-w") || !strcmp(arg, "--pass")) {
+ if (!(pass = irc_strsep(&args))) {
+ action(action_error, "connect: '-w/--pass' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-u") || !strcmp(arg, "--username")) {
+ if (!(username = irc_strsep(&args))) {
+ action(action_error, "connect: '-u/--username' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-r") || !strcmp(arg, "--realname")) {
+ if (!(realname = irc_strsep(&args))) {
+ action(action_error, "connect: '-r/--realname' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-m") || !strcmp(arg, "--mode")) {
+ if (!(mode = irc_strsep(&args))) {
+ action(action_error, "connect: '-m/--mode' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-n") || !strcmp(arg, "--nicks")) {
+ if (!(nicks = irc_strsep(&args))) {
+ action(action_error, "connect: '-n/--nicks' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "-c") || !strcmp(arg, "--chans")) {
+ if (!(chans = irc_strsep(&args))) {
+ action(action_error, "connect: '-c/--chans' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--tls-cert")) {
+ if (!(tls_cert = irc_strsep(&args))) {
+ action(action_error, "connect: '--tls-cert' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--tls-ca-file")) {
+ if (!(tls_ca_file = irc_strsep(&args))) {
+ action(action_error, "connect: '--tls-ca-file' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--tls-ca-path")) {
+ if (!(tls_ca_path = irc_strsep(&args))) {
+ action(action_error, "connect: '--tls-ca-path' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--tls-verify")) {
+ if (!(arg = irc_strsep(&args))) {
+ action(action_error, "connect: '--tls-verify' requires an argument");
+ return;
+ } else if (!strcmp(arg, "0") || !strcasecmp(arg, "disabled")) {
+ tls_vrfy = IO_TLS_VRFY_DISABLED;
+ } else if (!strcmp(arg, "1") || !strcasecmp(arg, "optional")) {
+ tls_vrfy = IO_TLS_VRFY_OPTIONAL;
+ } else if (!strcmp(arg, "2") || !strcasecmp(arg, "required")) {
+ tls_vrfy = IO_TLS_VRFY_REQUIRED;
+ } else {
+ action(action_error, "connect: invalid option for '--tls-verify' '%s'", arg);
+ return;
+ }
+ } else if (!strcmp(arg, "--tls-disable")) {
+ tls = IO_TLS_DISABLED;
+ } else if (!strcmp(arg, "--sasl")) {
+ if (!(sasl = irc_strsep(&args))) {
+ action(action_error, "connect: '--sasl' requires an argument");
+ return;
+ } else if (!strcasecmp(sasl, "EXTERNAL")) {
+ ;
+ } else if (!strcasecmp(sasl, "PLAIN")) {
+ ;
+ } else {
+ action(action_error, "connect: invalid option for '--sasl' '%s'", sasl);
+ return;
+ }
+ } else if (!strcmp(arg, "--sasl-user")) {
+ if (!(sasl_user = irc_strsep(&args))) {
+ action(action_error, "connect: '--sasl-user' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--sasl-pass")) {
+ if (!(sasl_pass = irc_strsep(&args))) {
+ action(action_error, "connect: '--sasl-pass' requires an argument");
+ return;
+ }
+ } else if (!strcmp(arg, "--ipv4")) {
+ ipv = IO_IPV_4;
+ } else if (!strcmp(arg, "--ipv6")) {
+ ipv = IO_IPV_6;
+ } else {
+ action(action_error, "connect: unknown option '%s'", arg);
+ return;
+ }
}
- if ((err = io_cx(c->server->connection)))
- action(action_error, "connect: %s", io_err(err));
+ if (port == NULL)
+ port = (tls == IO_TLS_ENABLED) ? "6697" : "6667";
- return;
- }
+ s = server(host, port, pass, username, realname, mode);
- if (!strcasecmp(cmd, "disconnect")) {
- if (!c->server) {
- action(action_error, "disconnect: This is not a server");
+ if (nicks && server_set_nicks(s, nicks)) {
+ action(action_error, "connect: invalid -n/--nicks: '%s'", nicks);
+ server_free(s);
return;
}
- if ((arg = irc_strsep(&buf))) {
- action(action_error, "disconnect: Unknown arg '%s'", arg);
+ if (chans && server_set_chans(s, chans)) {
+ action(action_error, "connect: invalid -c/--chans: '%s'", chans);
+ server_free(s);
return;
}
- if ((err = io_dx(c->server->connection)))
- action(action_error, "disconnect: %s", io_err(err));
+ if (server_list_add(state_server_list(), s)) {
+ action(action_error, "connect: duplicate server: %s:%s", host, port);
+ server_free(s);
+ return;
+ }
+
+ if (sasl)
+ server_set_sasl(s, sasl, sasl_user, sasl_pass);
+
+ s->connection = connection(
+ s,
+ host,
+ port,
+ tls_ca_file,
+ tls_ca_path,
+ tls_cert,
+ (ipv | tls | tls_vrfy));
+
+ if ((ret = io_cx(s->connection)))
+ server_error(s, "failed to connect: %s", io_err(ret));
+ channel_set_current(s->channel);
+ }
+}
+
+static void
+command_disconnect(struct channel *c, char *args)
+{
+ char *arg;
+ int err;
+
+ if (!c->server) {
+ action(action_error, "disconnect: This is not a server");
return;
}
- if (!strcasecmp(cmd, "quit")) {
- if ((arg = irc_strsep(&buf))) {
- action(action_error, "quit: Unknown arg '%s'", arg);
- return;
- }
+ if ((arg = irc_strsep(&args))) {
+ action(action_error, "disconnect: Unknown arg '%s'", arg);
+ return;
+ }
- io_stop();
+ if ((err = io_dx(c->server->connection)))
+ action(action_error, "disconnect: %s", io_err(err));
+}
+
+static void
+command_quit(struct channel *c, char *args)
+{
+ UNUSED(c);
+
+ char *arg;
+
+ if ((arg = irc_strsep(&args))) {
+ action(action_error, "quit: Unknown arg '%s'", arg);
return;
}
- action(action_error, "Unknown command '%s'", cmd);
+ io_stop();
}
static int
@@ 832,7 1053,7 @@ io_cb_read_soc(char *buf, size_t len, const void *cb_obj)
irc_recv(s, &m);
ci = 0;
- } else if (ci < IRC_MESSAGE_LEN && (isprint(cc) || cc == 0x01)) {
+ } else if (ci < IRC_MESSAGE_LEN && cc && cc != '\n' && cc != '\r') {
s->read.buf[ci++] = cc;
}
}
@@ 849,22 1070,23 @@ io_cb_cxed(const void *cb_obj)
struct server *s = (struct server *)cb_obj;
int ret;
+
server_reset(s);
server_nicks_next(s);
s->connected = 1;
if ((ret = io_sendf(s->connection, "CAP LS " IRCV3_CAP_VERSION)))
- newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
+ server_error(s, "sendf fail: %s", io_err(ret));
if (s->pass && (ret = io_sendf(s->connection, "PASS %s", s->pass)))
- newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
+ server_error(s, "sendf fail: %s", io_err(ret));
if ((ret = io_sendf(s->connection, "NICK %s", s->nick)))
- newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
+ server_error(s, "sendf fail: %s", io_err(ret));
- if ((ret = io_sendf(s->connection, "USER %s 8 * :%s", s->username, s->realname)))
- newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
+ if ((ret = io_sendf(s->connection, "USER %s 0 * :%s", s->username, s->realname)))
+ server_error(s, "sendf fail: %s", io_err(ret));
draw(DRAW_STATUS);
draw(DRAW_FLUSH);
@@ 876,7 1098,7 @@ io_cb_dxed(const void *cb_obj)
struct server *s = (struct server *)cb_obj;
struct channel *c = s->channel;
- s->connected = 0;
+ server_reset(s);
do {
newlinef(c, 0, FROM_ERROR, " -- disconnected --");
@@ 884,6 1106,7 @@ io_cb_dxed(const void *cb_obj)
c = c->next;
} while (c != s->channel);
+ draw(DRAW_STATUS);
draw(DRAW_FLUSH);
}
@@ 898,7 1121,7 @@ io_cb_ping(const void *cb_obj, unsigned ping)
if (ping != IO_PING_MIN)
draw(DRAW_STATUS);
else if ((ret = io_sendf(s->connection, "PING :%s", s->host)))
- newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
+ server_error(s, "sendf fail: %s", io_err(ret));
draw(DRAW_FLUSH);
}
D src/utils/list.h => src/utils/list.h +0 -6
@@ 1,6 0,0 @@
-#ifndef RIRC_UTILS_LIST_H
-#define RIRC_UTILS_LIST_H
-
-/* TODO: abstract the list implementations in server/channel */
-
-#endif
M test/components/ircv3.c => test/components/ircv3.c +2 -0
@@ 40,6 40,8 @@ test_ircv3_caps_reset(void)
{
struct ircv3_caps caps;
+ ircv3_caps(&caps);
+
caps.cap_3.req = 1;
caps.cap_3.req_auto = 1;
caps.cap_3.set = 1;
M test/components/mode.c => test/components/mode.c +118 -274
@@ 5,19 5,19 @@
#define ALL_UPPERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
static void
-test_flag_bit(void)
+test_mode_bit(void)
{
/* Test mode flag -> mode bit */
- assert_eq(flag_bit('a' - 1), 0);
- assert_eq(flag_bit('a'), 1 << 0);
- assert_eq(flag_bit('z'), 1 << 25);
- assert_eq(flag_bit('z' + 1), 0);
+ assert_eq(mode_bit('a' - 1), 0);
+ assert_eq(mode_bit('a'), 1 << 0);
+ assert_eq(mode_bit('z'), 1 << 25);
+ assert_eq(mode_bit('z' + 1), 0);
- assert_eq(flag_bit('A' - 1), 0);
- assert_eq(flag_bit('A'), 1 << 0);
- assert_eq(flag_bit('Z'), 1 << 25);
- assert_eq(flag_bit('Z' + 1), 0);
+ assert_eq(mode_bit('A' - 1), 0);
+ assert_eq(mode_bit('A'), 1 << 0);
+ assert_eq(mode_bit('Z'), 1 << 25);
+ assert_eq(mode_bit('Z' + 1), 0);
}
static void
@@ 28,274 28,156 @@ test_mode_str(void)
struct mode m = MODE_EMPTY;
struct mode_str str;
- memset(&str, 0, sizeof(struct mode_str));
-
- /* mode_str type not set */
- assert_fatal(mode_str(&m, &str));
-
- str.type = MODE_STR_T_SIZE;
-
/* mode_str type unknown */
- assert_fatal(mode_str(&m, &str));
-
- str.type = MODE_STR_USERMODE;
+ assert_fatal(mode_str(&m, &str, -1));
/* Test no mode */
- assert_strcmp(mode_str(&m, &str), "");
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), "");
m.lower = UINT32_MAX;
m.upper = 0;
- assert_strcmp(mode_str(&m, &str), ALL_LOWERS);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), ALL_LOWERS);
m.lower = 0;
m.upper = UINT32_MAX;
- assert_strcmp(mode_str(&m, &str), ALL_UPPERS);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), ALL_UPPERS);
m.lower = UINT32_MAX;
m.upper = UINT32_MAX;
- assert_strcmp(mode_str(&m, &str), ALL_LOWERS ALL_UPPERS);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), ALL_LOWERS ALL_UPPERS);
}
static void
-test_chanmode_set(void)
+test_mode_chanmode_set(void)
{
- /* Test setting/unsetting chanmode flag, prefix and mode string */
+ /* Test setting/unsetting chanmode flags */
struct mode m = MODE_EMPTY;
struct mode_cfg cfg;
- struct mode_str str = { .type = MODE_STR_CHANMODE };
+ struct mode_str str;
mode_cfg_chanmodes(&cfg, "abcdefghijsp");
mode_cfg_subtypes(&cfg, "abc,def,ghi,jsp");
-#define CHECK(STR, PRFX) \
- assert_strcmp(mode_str(&m, &str), (STR)); assert_eq(m.prefix, (PRFX));
-
/* Test setting/unsetting invalid chanmode flag */
- assert_eq(mode_chanmode_set(&m, &cfg, 'z', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
- CHECK("", 0);
- assert_eq(mode_chanmode_set(&m, &cfg, 'z', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
- CHECK("", 0);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'z', 1), MODE_ERR_INVALID_FLAG);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "");
+
+ assert_eq(mode_chanmode_set(&m, &cfg, 'z', 0), MODE_ERR_INVALID_FLAG);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "");
/* Test valid CHANMODE subtype A doesn't set flag */
- assert_eq(mode_chanmode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("", 0);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "");
/* Test valid CHANMODE subtypes B,C,D set flags */
- assert_eq(mode_chanmode_set(&m, &cfg, 'd', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("d", 0);
-
- assert_eq(mode_chanmode_set(&m, &cfg, 'e', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("de", 0);
-
- assert_eq(mode_chanmode_set(&m, &cfg, 'j', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("dej", 0);
-
- /* Test 's'/'p' chanmodes set prefix but do not appear in mode string
- *
- * Ensure 's' always supercedes and unsets 'p',
- * Ensure 'p' is silently ignored when 's' is set */
-
- assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("dejp", MODE_CHANMODE_PREFIX_PRIVATE);
- assert_true(mode_isset(&m, 'p'));
- assert_false(mode_isset(&m, 's'));
-
- /* Unsetting 'p' sets default */
- assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_OFF), MODE_ERR_NONE);
- CHECK("dej", MODE_CHANMODE_PREFIX_OTHER);
- assert_false(mode_isset(&m, 'p'));
- assert_false(mode_isset(&m, 's'));
-
- /* 's' supercedes 'p' */
- assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
- assert_eq(mode_chanmode_set(&m, &cfg, 's', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("dejs", MODE_CHANMODE_PREFIX_SECRET);
- assert_true(mode_isset(&m, 's'));
- assert_false(mode_isset(&m, 'p'));
-
- /* 'p' is silently ignored when 's' is set */
- assert_eq(mode_chanmode_set(&m, &cfg, 'p', MODE_SET_ON), MODE_ERR_NONE);
- CHECK("dejs", MODE_CHANMODE_PREFIX_SECRET);
- assert_true(mode_isset(&m, 's'));
- assert_false(mode_isset(&m, 'p'));
-
- /* Unsetting 's' sets default */
- assert_eq(mode_chanmode_set(&m, &cfg, 's', MODE_SET_OFF), MODE_ERR_NONE);
- CHECK("dej", MODE_CHANMODE_PREFIX_OTHER);
- assert_false(mode_isset(&m, 'p'));
- assert_false(mode_isset(&m, 's'));
-
- /* Test unsetting previous flags */
- assert_eq(mode_chanmode_set(&m, &cfg, 'j', MODE_SET_OFF), MODE_ERR_NONE);
- CHECK("de", MODE_CHANMODE_PREFIX_OTHER);
-
- assert_eq(mode_chanmode_set(&m, &cfg, 'e', MODE_SET_OFF), MODE_ERR_NONE);
- CHECK("d", MODE_CHANMODE_PREFIX_OTHER);
-
- assert_eq(mode_chanmode_set(&m, &cfg, 'd', MODE_SET_OFF), MODE_ERR_NONE);
- CHECK("", MODE_CHANMODE_PREFIX_OTHER);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'd', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "d");
-#undef CHECK
+ assert_eq(mode_chanmode_set(&m, &cfg, 'e', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "de");
+
+ assert_eq(mode_chanmode_set(&m, &cfg, 'j', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "dej");
+
+ /* test unsetting flags */
+ assert_eq(mode_chanmode_set(&m, &cfg, 's', 0), MODE_ERR_NONE);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'j', 0), MODE_ERR_NONE);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'e', 0), MODE_ERR_NONE);
+ assert_eq(mode_chanmode_set(&m, &cfg, 'd', 0), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_CHANMODE), "");
}
static void
-test_prfxmode_set(void)
+test_mode_prfxmode_set(void)
{
/* Test setting/unsetting prfxmode flag and prefix */
struct mode m = MODE_EMPTY;
-
struct mode_cfg cfg = {
.PREFIX = {
.F = "abc",
.T = "123"
}
};
+ struct mode_str str;
mode_cfg_chanmodes(&cfg, "abc");
/* Test setting/unsetting invalid prfxmode flag */
- assert_eq(mode_prfxmode_set(&m, &cfg, 'd', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'd', 1), MODE_ERR_INVALID_FLAG);
+ assert_eq(m.prefix, 0);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'd', 0), MODE_ERR_INVALID_FLAG);
+ assert_eq(m.prefix, 0);
+
+ /* Test setting/unsetting invalid prfxmode prefix */
+ assert_eq(mode_prfxmode_set(&m, &cfg, '4', 1), MODE_ERR_INVALID_FLAG);
assert_eq(m.prefix, 0);
- assert_eq(mode_prfxmode_set(&m, &cfg, 'd', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
+ assert_eq(mode_prfxmode_set(&m, &cfg, '4', 0), MODE_ERR_INVALID_FLAG);
assert_eq(m.prefix, 0);
/* Test setting valid flags respects PREFIX precedence */
- assert_eq(mode_prfxmode_set(&m, &cfg, 'b', MODE_SET_ON), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'b', 1), MODE_ERR_NONE);
assert_eq(m.prefix, '2');
- assert_eq(mode_prfxmode_set(&m, &cfg, 'c', MODE_SET_ON), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'c', 1), MODE_ERR_NONE);
assert_eq(m.prefix, '2');
- assert_eq(mode_prfxmode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
assert_eq(m.prefix, '1');
/* Test unsetting valid flags respects PREFIX precedence */
- assert_eq(mode_prfxmode_set(&m, &cfg, 'b', MODE_SET_OFF), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'b', 0), MODE_ERR_NONE);
assert_eq(m.prefix, '1');
- assert_eq(mode_prfxmode_set(&m, &cfg, 'a', MODE_SET_OFF), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'a', 0), MODE_ERR_NONE);
assert_eq(m.prefix, '3');
- assert_eq(mode_prfxmode_set(&m, &cfg, 'c', MODE_SET_OFF), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_set(&m, &cfg, 'c', 0), MODE_ERR_NONE);
assert_eq(m.prefix, 0);
+
+ /* Test setting valid prefixes */
+ assert_eq(mode_prfxmode_set(&m, &cfg, '2', 1), MODE_ERR_NONE);
+ assert_eq(m.prefix, '2');
+ assert_eq(mode_prfxmode_set(&m, &cfg, '3', 1), MODE_ERR_NONE);
+ assert_eq(m.prefix, '2');
+
+ assert_strcmp(mode_str(&m, &str, MODE_STR_PRFXMODE), "bc");
}
static void
-test_usermode_set(void)
+test_mode_usermode_set(void)
{
/* Test setting/unsetting usermode flag and mode string */
struct mode m = MODE_EMPTY;
- struct mode_str str = { .type = MODE_STR_USERMODE };
struct mode_cfg cfg;
+ struct mode_str str;
mode_cfg_usermodes(&cfg, "azAZ");
/* Test setting invalid usermode flag */
- assert_eq(mode_usermode_set(&m, &cfg, 'b', MODE_SET_ON), MODE_ERR_INVALID_FLAG);
+ assert_eq(mode_usermode_set(&m, &cfg, 'b', 1), MODE_ERR_INVALID_FLAG);
/* Test setting valid flags */
- assert_eq(mode_usermode_set(&m, &cfg, 'a', MODE_SET_ON), MODE_ERR_NONE);
- assert_eq(mode_usermode_set(&m, &cfg, 'Z', MODE_SET_ON), MODE_ERR_NONE);
- assert_strcmp(mode_str(&m, &str), "aZ");
+ assert_eq(mode_usermode_set(&m, &cfg, 'a', 1), MODE_ERR_NONE);
+ assert_eq(mode_usermode_set(&m, &cfg, 'Z', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), "aZ");
- assert_eq(mode_usermode_set(&m, &cfg, 'z', MODE_SET_ON), MODE_ERR_NONE);
- assert_eq(mode_usermode_set(&m, &cfg, 'A', MODE_SET_ON), MODE_ERR_NONE);
- assert_strcmp(mode_str(&m, &str), "azAZ");
+ assert_eq(mode_usermode_set(&m, &cfg, 'z', 1), MODE_ERR_NONE);
+ assert_eq(mode_usermode_set(&m, &cfg, 'A', 1), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), "azAZ");
/* Test unsetting invalid usermode flag */
- assert_eq(mode_usermode_set(&m, &cfg, 'c', MODE_SET_OFF), MODE_ERR_INVALID_FLAG);
+ assert_eq(mode_usermode_set(&m, &cfg, 'c', 0), MODE_ERR_INVALID_FLAG);
/* Test unsetting valid flags */
- assert_eq(mode_usermode_set(&m, &cfg, 'z', MODE_SET_OFF), MODE_ERR_NONE);
- assert_eq(mode_usermode_set(&m, &cfg, 'Z', MODE_SET_OFF), MODE_ERR_NONE);
- assert_strcmp(mode_str(&m, &str), "aA");
-
- assert_eq(mode_usermode_set(&m, &cfg, 'a', MODE_SET_OFF), MODE_ERR_NONE);
- assert_eq(mode_usermode_set(&m, &cfg, 'A', MODE_SET_OFF), MODE_ERR_NONE);
- assert_strcmp(mode_str(&m, &str), "");
-}
-
-static void
-test_chanmode_prefix(void)
-{
- /* Test setting chanmode by prefix */
-
- struct mode m = MODE_EMPTY;
- struct mode_cfg cfg;
- struct mode_str str = { .type = MODE_STR_CHANMODE };
-
- mode_cfg_chanmodes(&cfg, "sp");
-
-#define CHECK(M, PRFX, P, S, STR) \
- assert_eq((M).prefix, (PRFX)); \
- assert_eq(mode_isset(&(M), 'p'), (P)); \
- assert_eq(mode_isset(&(M), 's'), (S)); \
- assert_strcmp(mode_str(&(M), &str), (STR));
-
- /* Test setting invalid chanmode prefix */
- assert_eq(mode_chanmode_prefix(&m, &cfg, '$'), MODE_ERR_INVALID_PREFIX);
- CHECK(m, 0, 0, 0, "");
-
- /* Test setting valid chanmode prefixes by precedence*/
- assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
- CHECK(m, MODE_CHANMODE_PREFIX_OTHER, 0, 0, "");
-
- assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
- CHECK(m, MODE_CHANMODE_PREFIX_PRIVATE, 1, 0, "p");
-
- assert_eq(mode_chanmode_prefix(&m, &cfg, MODE_CHANMODE_PREFIX_SECRET), MODE_ERR_NONE);
- CHECK(m, MODE_CHANMODE_PREFIX_SECRET, 0, 1, "s");
-
- /* Test silently ignored setting by precedence */
-
- /* PRIVATE > OTHER */
- struct mode m2 = MODE_EMPTY;
-
- assert_eq(mode_chanmode_prefix(&m2, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
- assert_eq(mode_chanmode_prefix(&m2, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
- CHECK(m2, MODE_CHANMODE_PREFIX_PRIVATE, 1, 0, "p");
-
- /* SECRET > PRIVATE, OTHER */
- struct mode m3 = MODE_EMPTY;
-
- assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_SECRET), MODE_ERR_NONE);
- assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_PRIVATE), MODE_ERR_NONE);
- assert_eq(mode_chanmode_prefix(&m3, &cfg, MODE_CHANMODE_PREFIX_OTHER), MODE_ERR_NONE);
- CHECK(m3, MODE_CHANMODE_PREFIX_SECRET, 0, 1, "s");
-
-#undef CHECK
-}
-
-static void
-test_prfxmode_prefix(void)
-{
- /* Test setting prfxmode by prefix */
-
- struct mode m = MODE_EMPTY;
- struct mode_str str = { .type = MODE_STR_PRFXMODE };
-
- struct mode_cfg cfg = {
- .PREFIX = {
- .F = "abc",
- .T = "123"
- }
- };
-
- mode_cfg_chanmodes(&cfg, "abc");
-
- /* Test setting invalid prfxmode prefix */
- assert_eq(mode_prfxmode_prefix(&m, &cfg, 0), MODE_ERR_INVALID_PREFIX);
- assert_eq(mode_prfxmode_prefix(&m, &cfg, '0'), MODE_ERR_INVALID_PREFIX);
- assert_eq(mode_prfxmode_prefix(&m, &cfg, '4'), MODE_ERR_INVALID_PREFIX);
-
- /* Test setting valid prfxmode prefix */
- assert_eq(mode_prfxmode_prefix(&m, &cfg, '2'), MODE_ERR_NONE);
- assert_eq(mode_prfxmode_prefix(&m, &cfg, '3'), MODE_ERR_NONE);
+ assert_eq(mode_usermode_set(&m, &cfg, 'z', 0), MODE_ERR_NONE);
+ assert_eq(mode_usermode_set(&m, &cfg, 'Z', 0), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), "aA");
- assert_strcmp(mode_str(&m, &str), "bc");
- assert_eq(m.prefix, '2');
+ assert_eq(mode_usermode_set(&m, &cfg, 'a', 0), MODE_ERR_NONE);
+ assert_eq(mode_usermode_set(&m, &cfg, 'A', 0), MODE_ERR_NONE);
+ assert_strcmp(mode_str(&m, &str, MODE_STR_USERMODE), "");
}
static void
@@ 304,23 186,23 @@ test_mode_cfg_usermodes(void)
/* Test configuring server usermodes */
struct mode_cfg cfg;
- struct mode_str str = { .type = MODE_STR_USERMODE };
+ struct mode_str str;
/* Test empty string */
assert_eq(mode_cfg_usermodes(&cfg, ""), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.usermodes), &str), "");
+ assert_strcmp(mode_str(&(cfg.usermodes), &str, MODE_STR_USERMODE), "");
/* Test invalid flags */
assert_eq(mode_cfg_usermodes(&cfg, "$abc1!xyz."), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.usermodes), &str), "abcxyz");
+ assert_strcmp(mode_str(&(cfg.usermodes), &str, MODE_STR_USERMODE), "abcxyz");
/* Test duplicate flags */
assert_eq(mode_cfg_usermodes(&cfg, "aaabbc"), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.usermodes), &str), "abc");
+ assert_strcmp(mode_str(&(cfg.usermodes), &str, MODE_STR_USERMODE), "abc");
/* Test valid string */
assert_eq(mode_cfg_usermodes(&cfg, ALL_LOWERS ALL_UPPERS), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.usermodes), &str), ALL_LOWERS ALL_UPPERS);
+ assert_strcmp(mode_str(&(cfg.usermodes), &str, MODE_STR_USERMODE), ALL_LOWERS ALL_UPPERS);
}
static void
@@ 329,23 211,23 @@ test_mode_cfg_chanmodes(void)
/* Test configuring server chanmodes */
struct mode_cfg cfg;
- struct mode_str str = { .type = MODE_STR_USERMODE };
+ struct mode_str str;
/* Test empty string */
assert_eq(mode_cfg_chanmodes(&cfg, ""), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.chanmodes), &str), "");
+ assert_strcmp(mode_str(&(cfg.chanmodes), &str, MODE_STR_USERMODE), "");
/* Test invalid flags */
assert_eq(mode_cfg_chanmodes(&cfg, "$abc1!xyz."), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.chanmodes), &str), "abcxyz");
+ assert_strcmp(mode_str(&(cfg.chanmodes), &str, MODE_STR_USERMODE), "abcxyz");
/* Test duplicate flags */
assert_eq(mode_cfg_chanmodes(&cfg, "aaabbc"), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.chanmodes), &str), "abc");
+ assert_strcmp(mode_str(&(cfg.chanmodes), &str, MODE_STR_USERMODE), "abc");
/* Test valid string */
assert_eq(mode_cfg_chanmodes(&cfg, ALL_LOWERS ALL_UPPERS), MODE_ERR_NONE);
- assert_strcmp(mode_str(&(cfg.chanmodes), &str), ALL_LOWERS ALL_UPPERS);
+ assert_strcmp(mode_str(&(cfg.chanmodes), &str, MODE_STR_USERMODE), ALL_LOWERS ALL_UPPERS);
}
static void
@@ 354,13 236,13 @@ test_mode_cfg_subtypes(void)
/* Test configuring CHANMODE subtypes */
struct mode_cfg cfg;
- struct mode_str str = { .type = MODE_STR_USERMODE };
+ struct mode_str str;
#define CHECK(_A, _B, _C, _D) \
- assert_strcmp(mode_str(&(cfg.CHANMODES.A), &str), (_A)); \
- assert_strcmp(mode_str(&(cfg.CHANMODES.B), &str), (_B)); \
- assert_strcmp(mode_str(&(cfg.CHANMODES.C), &str), (_C)); \
- assert_strcmp(mode_str(&(cfg.CHANMODES.D), &str), (_D));
+ assert_strcmp(mode_str(&(cfg.CHANMODES.A), &str, MODE_STR_USERMODE), (_A)); \
+ assert_strcmp(mode_str(&(cfg.CHANMODES.B), &str, MODE_STR_USERMODE), (_B)); \
+ assert_strcmp(mode_str(&(cfg.CHANMODES.C), &str, MODE_STR_USERMODE), (_C)); \
+ assert_strcmp(mode_str(&(cfg.CHANMODES.D), &str, MODE_STR_USERMODE), (_D));
/* Test empty string */
assert_eq(mode_cfg_subtypes(&cfg, ""), MODE_ERR_NONE);
@@ 372,15 254,11 @@ test_mode_cfg_subtypes(void)
/* Test extra commas */
assert_eq(mode_cfg_subtypes(&cfg, "abc,def,,xyz,,,abc"), MODE_ERR_INVALID_CONFIG);
- CHECK("abc", "def", "", "xyz");
+ CHECK("", "", "", "");
/* Test invalid flags */
- assert_eq(mode_cfg_subtypes(&cfg, "!!abc,d123e,fg!-@,^&"), MODE_ERR_NONE);
- CHECK("abc", "de", "fg", "");
-
- /* Test duplicate flags */
- assert_eq(mode_cfg_subtypes(&cfg, "zaabc,deefz,zghh,zzz"), MODE_ERR_NONE);
- CHECK("abcz", "def", "gh", "");
+ assert_eq(mode_cfg_subtypes(&cfg, "!!abc,d123e,fg!-@,^&"), MODE_ERR_INVALID_CONFIG);
+ CHECK("", "", "", "");
const char *all_flags =
ALL_LOWERS ALL_UPPERS ","
@@ 390,7 268,10 @@ test_mode_cfg_subtypes(void)
/* Test valid string */
assert_eq(mode_cfg_subtypes(&cfg, all_flags), MODE_ERR_NONE);
- CHECK(ALL_LOWERS ALL_UPPERS, "", "", "");
+ CHECK(ALL_LOWERS ALL_UPPERS,
+ ALL_LOWERS ALL_UPPERS,
+ ALL_LOWERS ALL_UPPERS,
+ ALL_LOWERS ALL_UPPERS);
#undef CHECK
}
@@ 450,38 331,7 @@ test_mode_cfg_prefix(void)
}
static void
-test_mode_cfg_modes(void)
-{
- /* Test configuring MODES */
-
- struct mode_cfg cfg = {
- .MODES = 3
- };
-
- /* Test empty string */
- assert_eq(mode_cfg_modes(&cfg, ""), MODE_ERR_INVALID_CONFIG);
- assert_eq(cfg.MODES, 3);
-
- /* Test not a number */
- assert_eq(mode_cfg_modes(&cfg, "1abc"), MODE_ERR_INVALID_CONFIG);
- assert_eq(mode_cfg_modes(&cfg, "wxyz"), MODE_ERR_INVALID_CONFIG);
- assert_eq(cfg.MODES, 3);
-
- /* Test invalid number (i.e.: not [1-99]) */
- assert_eq(mode_cfg_modes(&cfg, "0"), MODE_ERR_INVALID_CONFIG);
- assert_eq(mode_cfg_modes(&cfg, "100"), MODE_ERR_INVALID_CONFIG);
- assert_eq(cfg.MODES, 3);
-
- /* Teset valid numbers */
- assert_eq(mode_cfg_modes(&cfg, "1"), MODE_ERR_NONE);
- assert_eq(cfg.MODES, 1);
-
- assert_eq(mode_cfg_modes(&cfg, "99"), MODE_ERR_NONE);
- assert_eq(cfg.MODES, 99);
-}
-
-static void
-test_chanmode_type(void)
+test_mode_type(void)
{
/* Test retrieving a mode flag type */
@@ 497,53 347,47 @@ test_chanmode_type(void)
if (config_errs != MODE_ERR_NONE)
test_abort("Configuration error");
- /* Test invalid '+'/'-' */
- assert_eq(chanmode_type(&cfg, MODE_SET_INVALID, 'a'), MODE_FLAG_INVALID_SET);
-
/* Test invalid flag */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, '!'), MODE_FLAG_INVALID_FLAG);
+ assert_eq(mode_type(&cfg, '!', 1), MODE_FLAG_INVALID_FLAG);
/* Test flag not in usermodes, chanmodes */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'z'), MODE_FLAG_INVALID_FLAG);
+ assert_eq(mode_type(&cfg, 'z', 1), MODE_FLAG_INVALID_FLAG);
/* Test chanmode A (always has a parameter) */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'b'), MODE_FLAG_CHANMODE_PARAM);
- assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'b'), MODE_FLAG_CHANMODE_PARAM);
+ assert_eq(mode_type(&cfg, 'b', 1), MODE_FLAG_CHANMODE_PARAM);
+ assert_eq(mode_type(&cfg, 'b', 0), MODE_FLAG_CHANMODE_PARAM);
/* Test chanmode B (always has a parameter) */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'c'), MODE_FLAG_CHANMODE_PARAM);
- assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'c'), MODE_FLAG_CHANMODE_PARAM);
+ assert_eq(mode_type(&cfg, 'c', 1), MODE_FLAG_CHANMODE_PARAM);
+ assert_eq(mode_type(&cfg, 'c', 0), MODE_FLAG_CHANMODE_PARAM);
/* Test chanmode C (only has a parameter when set) */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'd'), MODE_FLAG_CHANMODE_PARAM);
- assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'd'), MODE_FLAG_CHANMODE);
+ assert_eq(mode_type(&cfg, 'd', 1), MODE_FLAG_CHANMODE_PARAM);
+ assert_eq(mode_type(&cfg, 'd', 0), MODE_FLAG_CHANMODE);
/* Test chanmode D (never has a parameter) */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'e'), MODE_FLAG_CHANMODE);
- assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'e'), MODE_FLAG_CHANMODE);
+ assert_eq(mode_type(&cfg, 'e', 1), MODE_FLAG_CHANMODE);
+ assert_eq(mode_type(&cfg, 'e', 0), MODE_FLAG_CHANMODE);
/* Test prefix flag */
- assert_eq(chanmode_type(&cfg, MODE_SET_ON, 'f'), MODE_FLAG_PREFIX);
- assert_eq(chanmode_type(&cfg, MODE_SET_OFF, 'f'), MODE_FLAG_PREFIX);
+ assert_eq(mode_type(&cfg, 'f', 1), MODE_FLAG_PREFIX);
+ assert_eq(mode_type(&cfg, 'f', 0), MODE_FLAG_PREFIX);
}
int
main(void)
{
struct testcase tests[] = {
- TESTCASE(test_flag_bit),
+ TESTCASE(test_mode_bit),
TESTCASE(test_mode_str),
- TESTCASE(test_chanmode_set),
- TESTCASE(test_prfxmode_set),
- TESTCASE(test_usermode_set),
- TESTCASE(test_chanmode_prefix),
- TESTCASE(test_prfxmode_prefix),
+ TESTCASE(test_mode_chanmode_set),
+ TESTCASE(test_mode_prfxmode_set),
+ TESTCASE(test_mode_usermode_set),
TESTCASE(test_mode_cfg_usermodes),
TESTCASE(test_mode_cfg_chanmodes),
TESTCASE(test_mode_cfg_subtypes),
TESTCASE(test_mode_cfg_prefix),
- TESTCASE(test_mode_cfg_modes),
- TESTCASE(test_chanmode_type)
+ TESTCASE(test_mode_type)
};
return run_tests(NULL, NULL, tests);
M test/components/server.c => test/components/server.c +16 -9
@@ 44,13 44,13 @@ test_server_list(void)
memset(&servers, 0, sizeof(servers));
- s1 = server("host1", "port1", NULL, "", "");
- s2 = server("host1", "port1", "foo1", "", ""); /* duplicate host, port (s1) */
- s3 = server("host1", "port2", "foo2", "", ""); /* duplicate host (s1), different port */
- s4 = server("host2", "port1", "foo3", "", ""); /* duplicate port (s1), different host */
- s5 = server("host2", "port2", NULL, "", ""); /* duplicate host (s4), duplicate port (s3) */
- s6 = server("host1", "port2", NULL, "", ""); /* duplicate host, port (s4) */
- s7 = server("host2", "port1", NULL, "", ""); /* duplicate host, port (s5) */
+ s1 = server("host1", "port1", NULL, "real", "user", "abc");
+ s2 = server("host1", "port1", "foo1", "real", "user", "abc"); /* duplicate host, port (s1) */
+ s3 = server("host1", "port2", "foo2", "real", "user", "abc"); /* duplicate host (s1), different port */
+ s4 = server("host2", "port1", "foo3", "real", "user", NULL); /* duplicate port (s1), different host */
+ s5 = server("host2", "port2", NULL, "real", "user", NULL); /* duplicate host (s4), duplicate port (s3) */
+ s6 = server("host1", "port2", NULL, "real", "user", NULL); /* duplicate host, port (s4) */
+ s7 = server("host2", "port1", NULL, "real", "user", NULL); /* duplicate host, port (s5) */
/* Test add */
assert_ptr_eq(server_list_add(&servers, s1), NULL);
@@ 117,7 117,7 @@ test_server_set_chans(void)
struct channel *c;
struct server *s;
- s = server("host1", "port", NULL, "", "");
+ s = server("host1", "port", NULL, "real", "user", NULL);
assert_eq(s->clist.count, 1);
/* empty values, invalid formats */
@@ 162,7 162,7 @@ test_server_set_chans(void)
static void
test_server_set_nicks(void)
{
- struct server *s = server("host", "port", NULL, "", "");
+ struct server *s = server("host", "port", NULL, "real", "user", NULL);
server_nicks_next(s);
assert_strncmp(s->nick, "rirc", 4);
@@ 213,6 213,12 @@ test_server_set_nicks(void)
}
static void
+test_server_set_sasl(void)
+{
+ // TODO
+}
+
+static void
test_parse_005(void)
{
/* Test numeric 005 parsing */
@@ 301,6 307,7 @@ main(void)
TESTCASE(test_server_list),
TESTCASE(test_server_set_chans),
TESTCASE(test_server_set_nicks),
+ TESTCASE(test_server_set_sasl),
TESTCASE(test_parse_005)
};
M test/handlers/irc_ctcp.c => test/handlers/irc_ctcp.c +1 -1
@@ 483,7 483,7 @@ test_recv_ctcp_response_version(void)
static int
test_init(void)
{
- s = server("h1", "p1", NULL, "u1", "r1");
+ s = server("h1", "p1", NULL, "u1", "r1", NULL);
c_chan = channel("chan", CHANNEL_T_CHANNEL);
c_priv = channel("nick", CHANNEL_T_PRIVMSG);
M test/handlers/irc_recv.c => test/handlers/irc_recv.c +69 -90
@@ 44,8 44,6 @@ static struct server *s;
static void
test_irc_generic(void)
{
- server_reset(s);
-
#define CHECK_IRC_GENERIC(M, C, F, RET, LINE_N) \
do { \
mock_reset_io(); \
@@ 92,8 90,6 @@ test_irc_generic(void)
static void
test_irc_generic_error(void)
{
- server_reset(s);
-
char m1[] = "COMMAND arg1 arg2 :trailing arg";
mock_reset_io();
@@ 108,8 104,6 @@ test_irc_generic_error(void)
static void
test_irc_generic_ignore(void)
{
- server_reset(s);
-
char m1[] = "COMMAND arg1 arg2 :trailing arg";
mock_reset_io();
@@ 123,8 117,6 @@ test_irc_generic_ignore(void)
static void
test_irc_generic_info(void)
{
- server_reset(s);
-
char m1[] = "COMMAND arg1 arg2 :trailing arg";
mock_reset_io();
@@ 139,8 131,6 @@ test_irc_generic_info(void)
static void
test_irc_generic_unknown(void)
{
- server_reset(s);
-
char m1[] = "COMMAND arg1 arg2 :trailing arg";
mock_reset_io();
@@ 153,6 143,66 @@ test_irc_generic_unknown(void)
}
static void
+test_irc_numeric_001(void)
+{
+ /* 001 :<Welcome message> */
+
+ /* test registered */
+ mock_reset_io();
+ mock_reset_state();
+
+ assert_eq(s->registered, 0);
+
+ CHECK_RECV("001 me", 0, 1, 3);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "You are known as me");
+ assert_strcmp(mock_send[0], "JOIN #c1");
+ assert_strcmp(mock_send[1], "JOIN #c2");
+ assert_strcmp(mock_send[2], "JOIN #c3");
+
+ assert_eq(s->registered, 1);
+
+ /* test welcome message */
+ mock_reset_io();
+ mock_reset_state();
+
+ CHECK_RECV("001 me :welcome message", 0, 2, 3);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "welcome message");
+ assert_strcmp(mock_line[1], "You are known as me");
+ assert_strcmp(mock_send[0], "JOIN #c1");
+ assert_strcmp(mock_send[1], "JOIN #c2");
+ assert_strcmp(mock_send[2], "JOIN #c3");
+
+ /* test user modes */
+ mock_reset_io();
+ mock_reset_state();
+
+ s->mode = strdup("abc");
+
+ CHECK_RECV("001 me", 0, 1, 4);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "You are known as me");
+ assert_strcmp(mock_send[0], "MODE me +abc");
+ assert_strcmp(mock_send[1], "JOIN #c1");
+ assert_strcmp(mock_send[2], "JOIN #c2");
+ assert_strcmp(mock_send[3], "JOIN #c3");
+
+ /* test parted channels aren't auto joined */
+ mock_reset_io();
+ mock_reset_state();
+
+ c2->parted = 1;
+
+ CHECK_RECV("001 me", 0, 1, 3);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "You are known as me");
+ assert_strcmp(mock_send[0], "MODE me +abc");
+ assert_strcmp(mock_send[1], "JOIN #c1");
+ assert_strcmp(mock_send[2], "JOIN #c3");
+}
+
+static void
test_irc_numeric_353(void)
{
/* 353 <nick> <type> <channel> 1*(<modes><nick>) */
@@ 162,9 212,6 @@ test_irc_numeric_353(void)
struct user *u3;
struct user *u4;
- channel_reset(c1);
- server_reset(s);
-
/* test errors */
CHECK_RECV("353 me", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
@@ 181,7 228,7 @@ test_irc_numeric_353(void)
assert_strcmp(mock_line[0], "RPL_NAMEREPLY: channel '#x' not found");
CHECK_RECV("353 me x #c1 :n1", 1, 1, 0);
- assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid channel flag: 'x'");
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid channel type: 'x'");
CHECK_RECV("353 me = #c1 :+@", 1, 1, 0);
assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid nick: '+@'");
@@ 208,8 255,8 @@ test_irc_numeric_353(void)
|| !(u3 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n3", 0)))
test_abort("Failed to retrieve users");
- assert_eq(u1->prfxmodes.lower, (flag_bit('o')));
- assert_eq(u2->prfxmodes.lower, (flag_bit('v')));
+ assert_eq(u1->prfxmodes.lower, (mode_bit('o')));
+ assert_eq(u2->prfxmodes.lower, (mode_bit('v')));
assert_eq(u3->prfxmodes.lower, 0);
/* test multiple nicks, multiprefix enabled */
@@ 228,10 275,10 @@ test_irc_numeric_353(void)
assert_eq(u2->prfxmodes.prefix, '+');
assert_eq(u3->prfxmodes.prefix, '@');
assert_eq(u4->prfxmodes.prefix, '@');
- assert_eq(u1->prfxmodes.lower, (flag_bit('o')));
- assert_eq(u2->prfxmodes.lower, (flag_bit('v')));
- assert_eq(u3->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
- assert_eq(u4->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
+ assert_eq(u1->prfxmodes.lower, (mode_bit('o')));
+ assert_eq(u2->prfxmodes.lower, (mode_bit('v')));
+ assert_eq(u3->prfxmodes.lower, (mode_bit('o') | mode_bit('v')));
+ assert_eq(u4->prfxmodes.lower, (mode_bit('o') | mode_bit('v')));
}
static void
@@ 239,8 286,6 @@ test_irc_numeric_401(void)
{
/* <nick> :No such nick/channel */
- server_reset(s);
-
/* test errors */
CHECK_RECV("401 me", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
@@ 277,8 322,6 @@ test_irc_numeric_403(void)
{
/* <chan> :No such channel */
- server_reset(s);
-
/* test errors */
CHECK_RECV("403 me", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
@@ 313,8 356,6 @@ test_irc_numeric_403(void)
static void
test_recv(void)
{
- server_reset(s);
-
/* test unhandled command type */
CHECK_RECV("UNHANDLED", 0, 1, 0);
assert_strcmp(mock_chan[0], "host");
@@ 326,8 367,6 @@ test_recv_error(void)
{
/* ERROR :<message> */
- server_reset(s);
-
CHECK_RECV("ERROR", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
assert_strcmp(mock_line[0], "ERROR: message is null");
@@ 349,9 388,6 @@ test_recv_invite(void)
{
/* :nick!user@host INVITE <nick> <channel> */
- channel_reset(c1);
- server_reset(s);
-
CHECK_RECV("INVITE nick channel", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
assert_strcmp(mock_line[0], "INVITE: sender's nick is null");
@@ 380,10 416,6 @@ test_recv_join(void)
/* :nick!user@host JOIN <channel>
* :nick!user@host JOIN <channel> <account> :<realname> */
- channel_reset(c1);
- channel_reset(c2);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c2->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
@@ 443,15 475,6 @@ test_recv_kick(void)
{
/* :nick!user@host KICK <channel> <user> [:message] */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
- c1->parted = 0;
- c2->parted = 0;
- c3->parted = 0;
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
@@ 527,11 550,6 @@ test_recv_nick(void)
{
/* :nick!user@host NICK <nick> */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
@@ 585,8 603,6 @@ test_recv_notice(void)
static void
test_recv_numeric(void)
{
- server_reset(s);
-
CHECK_RECV(":hostname 0 * arg :trailing arg", 1, 1, 0);
assert_strcmp(mock_line[0], "NUMERIC: '0' invalid");
@@ 625,15 641,6 @@ test_recv_part(void)
{
/* :nick!user@host PART <channel> [:message] */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
- c1->parted = 0;
- c2->parted = 0;
- c3->parted = 0;
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
@@ 703,8 710,6 @@ test_recv_ping(void)
{
/* PING <server> */
- server_reset(s);
-
CHECK_RECV("PING", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
assert_strcmp(mock_line[0], "PING: server is null");
@@ 718,8 723,6 @@ test_recv_pong(void)
{
/* PONG <server> [<server2>] */
- server_reset(s);
-
CHECK_RECV("PONG", 0, 0, 0);
CHECK_RECV("PONG s1", 0, 0, 0);
CHECK_RECV("PONG s1 s2", 0, 0, 0);
@@ 736,11 739,6 @@ test_recv_quit(void)
{
/* :nick!user@host QUIT [:message] */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
@@ 794,9 792,6 @@ test_recv_topic(void)
{
/* :nick!user@host TOPIC <channel> [:topic] */
- channel_reset(c1);
- server_reset(s);
-
CHECK_RECV("TOPIC #c1 message", 1, 1, 0);
assert_strcmp(mock_chan[0], "host");
assert_strcmp(mock_line[0], "TOPIC: sender's nick is null");
@@ 832,8 827,6 @@ test_recv_ircv3_cap(void)
{
/* Full IRCv3 CAP coverage in test/handlers/ircv3.c */
- server_reset(s);
-
s->registered = 1;
CHECK_RECV("CAP * LS :cap-1 cap-2 cap-3", 0, 1, 0);
@@ 846,11 839,6 @@ test_recv_ircv3_account(void)
{
/* :nick!user@host ACCOUNT <account> */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
@@ 892,11 880,6 @@ test_recv_ircv3_away(void)
{
/* :nick!user@host AWAY [:message] */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
@@ 935,11 918,6 @@ test_recv_ircv3_chghost(void)
{
/* :nick!user@host CHGHOST new_user new_host */
- channel_reset(c1);
- channel_reset(c2);
- channel_reset(c3);
- server_reset(s);
-
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
@@ 975,7 953,7 @@ test_recv_ircv3_chghost(void)
static int
test_init(void)
{
- s = server("host", "port", NULL, "user", "real");
+ s = server("host", "port", NULL, "user", "real", NULL);
c1 = channel("#c1", CHANNEL_T_CHANNEL);
c2 = channel("#c2", CHANNEL_T_CHANNEL);
@@ 1018,6 996,7 @@ main(void)
TESTCASE(test_irc_generic_ignore),
TESTCASE(test_irc_generic_info),
TESTCASE(test_irc_generic_unknown),
+ TESTCASE(test_irc_numeric_001),
TESTCASE(test_irc_numeric_353),
TESTCASE(test_irc_numeric_401),
TESTCASE(test_irc_numeric_403),
M test/handlers/irc_send.c => test/handlers/irc_send.c +1 -1
@@ 465,7 465,7 @@ test_send_ircv3_cap_list(void)
static int
test_init(void)
{
- s = server("h1", "p1", NULL, "u1", "r1");
+ s = server("h1", "p1", NULL, "u1", "r1", NULL);
c_serv = s->channel;
M test/handlers/ircv3.c => test/handlers/ircv3.c +485 -40
@@ 42,50 42,50 @@ mock_reset(void)
}
static void
-test_ircv3_CAP(void)
+test_ircv3_recv_CAP(void)
{
mock_reset();
IRC_MESSAGE_PARSE("CAP");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_strcmp(mock_line[0], "CAP: target is null");
mock_reset();
IRC_MESSAGE_PARSE("CAP *");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_strcmp(mock_line[0], "CAP: command is null");
mock_reset();
IRC_MESSAGE_PARSE("CAP * ack");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_strcmp(mock_line[0], "CAP: unrecognized subcommand 'ack'");
mock_reset();
IRC_MESSAGE_PARSE("CAP * xxx");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_strcmp(mock_line[0], "CAP: unrecognized subcommand 'xxx'");
}
static void
-test_ircv3_CAP_LS(void)
+test_ircv3_recv_CAP_LS(void)
{
/* test empty LS, no parameter */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LS");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_strcmp(mock_line[0], "CAP LS: parameter is null");
/* test empty LS, no parameter, multiline */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LS *");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_strcmp(mock_line[0], "CAP LS: parameter is null");
/* test multiple parameters, no '*' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LS cap-1 cap-2");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_strcmp(mock_line[0], "CAP LS: invalid parameters");
@@ 135,6 135,20 @@ test_ircv3_CAP_LS(void)
assert_eq(s->ircv3_caps.cap_2.supported, 1);
assert_eq(s->ircv3_caps.cap_3.supported, 1);
+ /* test cap key=val */
+ mock_reset();
+ ircv3_caps_reset(&(s->ircv3_caps));
+ IRC_MESSAGE_PARSE("CAP * LS :cap-1=foo cap-3 cap-4=bar");
+ assert_eq(irc_recv(s, &m), 0);
+ assert_eq(mock_send_n, 1);
+ assert_strcmp(mock_send[0], "CAP REQ :cap-1 cap-3 cap-4");
+ assert_eq(s->ircv3_caps.cap_1.supported, 1);
+ assert_eq(s->ircv3_caps.cap_3.supported, 1);
+ assert_eq(s->ircv3_caps.cap_4.supported, 1);
+ assert_strcmp(s->ircv3_caps.cap_1.val, "foo");
+ assert_strcmp(s->ircv3_caps.cap_3.val, NULL);
+ assert_strcmp(s->ircv3_caps.cap_4.val, "bar");
+
/* test multiline */
mock_reset();
ircv3_caps_reset(&(s->ircv3_caps));
@@ 164,12 178,12 @@ test_ircv3_CAP_LS(void)
}
static void
-test_ircv3_CAP_LIST(void)
+test_ircv3_recv_CAP_LIST(void)
{
/* test empty LIST, no parameter */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LIST");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP LIST: parameter is null");
@@ 177,7 191,7 @@ test_ircv3_CAP_LIST(void)
/* test empty LIST, no parameter, multiline */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LIST *");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP LIST: parameter is null");
@@ 185,7 199,7 @@ test_ircv3_CAP_LIST(void)
/* test multiple parameters, no '*' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * LIST cap-1 cap-2");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP LIST: invalid parameters");
@@ 236,12 250,12 @@ test_ircv3_CAP_LIST(void)
}
static void
-test_ircv3_CAP_ACK(void)
+test_ircv3_recv_CAP_ACK(void)
{
/* test empty ACK */
mock_reset();
IRC_MESSAGE_PARSE("CAP * ACK");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP ACK: parameter is null");
@@ 249,19 263,35 @@ test_ircv3_CAP_ACK(void)
/* test empty ACK, with leading ':' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * ACK :");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP ACK: parameter is empty");
- /* unregisterd, error */
+ /* test ack key=val */
+ mock_reset();
+ s->ircv3_caps.cap_1.req = 1;
+ s->ircv3_caps.cap_2.req = 1;
+ s->ircv3_caps.cap_3.req = 1;
+ s->ircv3_caps.cap_1.val = strdup("foo");
+ s->ircv3_caps.cap_3.val = strdup("bar");
+ IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-2 cap-3");
+ assert_eq(irc_recv(s, &m), 0);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 3);
+ assert_strcmp(mock_line[0], "capability change accepted: cap-1=foo");
+ assert_strcmp(mock_line[1], "capability change accepted: cap-2");
+ assert_strcmp(mock_line[2], "capability change accepted: cap-3=bar");
+ assert_strcmp(mock_send[0], "CAP END");
+
+ /* test unregisterd, error */
mock_reset();
s->ircv3_caps.cap_1.set = 0;
s->ircv3_caps.cap_2.set = 0;
s->ircv3_caps.cap_1.req = 1;
s->ircv3_caps.cap_2.req = 1;
IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-aaa cap-2 cap-bbb");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 5);
assert_strcmp(mock_line[0], "capability change accepted: cap-1");
@@ 270,7 300,7 @@ test_ircv3_CAP_ACK(void)
assert_strcmp(mock_line[3], "CAP ACK: 'cap-bbb' not supported");
assert_strcmp(mock_line[4], "CAP ACK: parameter errors");
- /* unregistered, no error */
+ /* test unregistered, no error */
mock_reset();
s->ircv3_caps.cap_1.set = 0;
s->ircv3_caps.cap_2.set = 1;
@@ 293,7 323,7 @@ test_ircv3_CAP_ACK(void)
assert_strcmp(mock_line[2], "capability change accepted: -cap-3");
assert_strcmp(mock_send[0], "CAP END");
- /* registered, error */
+ /* test registered, error */
mock_reset();
s->registered = 1;
s->ircv3_caps.cap_1.set = 0;
@@ 305,7 335,7 @@ test_ircv3_CAP_ACK(void)
s->ircv3_caps.cap_3.req = 1;
s->ircv3_caps.cap_4.req = 0;
IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-2 -cap-3 -cap-4");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 5);
assert_strcmp(mock_line[0], "capability change accepted: cap-1");
@@ 314,7 344,7 @@ test_ircv3_CAP_ACK(void)
assert_strcmp(mock_line[3], "CAP ACK: '-cap-4' was not requested");
assert_strcmp(mock_line[4], "CAP ACK: parameter errors");
- /* registered, no error */
+ /* test registered, no error */
mock_reset();
s->registered = 1;
s->ircv3_caps.cap_1.set = 0;
@@ 332,12 362,12 @@ test_ircv3_CAP_ACK(void)
}
static void
-test_ircv3_CAP_NAK(void)
+test_ircv3_recv_CAP_NAK(void)
{
/* test empty NAK */
mock_reset();
IRC_MESSAGE_PARSE("CAP * NAK");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP NAK: parameter is null");
@@ 345,7 375,7 @@ test_ircv3_CAP_NAK(void)
/* test empty NAK, with leading ':' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * NAK :");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP NAK: parameter is empty");
@@ 406,12 436,12 @@ test_ircv3_CAP_NAK(void)
}
static void
-test_ircv3_CAP_DEL(void)
+test_ircv3_recv_CAP_DEL(void)
{
/* test empty DEL */
mock_reset();
IRC_MESSAGE_PARSE("CAP * DEL");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP DEL: parameter is null");
@@ 419,7 449,7 @@ test_ircv3_CAP_DEL(void)
/* test empty DEL, with leading ':' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * DEL :");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP DEL: parameter is empty");
@@ 427,7 457,7 @@ test_ircv3_CAP_DEL(void)
/* test cap-7 doesn't support DEL */
mock_reset();
IRC_MESSAGE_PARSE("CAP * DEL :cap-7");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP DEL: 'cap-7' doesn't support DEL");
@@ 462,12 492,12 @@ test_ircv3_CAP_DEL(void)
}
static void
-test_ircv3_CAP_NEW(void)
+test_ircv3_recv_CAP_NEW(void)
{
/* test empty NEW */
mock_reset();
IRC_MESSAGE_PARSE("CAP * NEW");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP NEW: parameter is null");
@@ 475,7 505,7 @@ test_ircv3_CAP_NEW(void)
/* test empty DEL, with leading ':' */
mock_reset();
IRC_MESSAGE_PARSE("CAP * NEW :");
- assert_eq(irc_recv(s, &m), 1);
+ assert_eq(irc_recv(s, &m), -1);
assert_eq(mock_send_n, 0);
assert_eq(mock_line_n, 1);
assert_strcmp(mock_line[0], "CAP NEW: parameter is empty");
@@ 550,6 580,312 @@ test_ircv3_CAP_NEW(void)
assert_eq(s->ircv3_caps.cap_7.supported, 0);
}
+
+static void
+test_ircv3_recv_AUTHENTICATE(void)
+{
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 0);
+ assert_eq(mock_line_n, 1);
+ assert_strcmp(mock_line[0], "AUTHENTICATE: no SASL mechanism");
+
+ s->ircv3_sasl.mech = -1;
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_fatal(irc_recv(s, &m));
+}
+
+static void
+test_ircv3_recv_AUTHENTICATE_EXTERNAL(void)
+{
+ server_set_sasl(s, "external", NULL, NULL);
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 0);
+ assert_eq(mock_line_n, 1);
+ assert_strcmp(mock_line[0], "Invalid SASL state for mechanism EXTERNAL: 0");
+
+ assert_eq(ircv3_sasl_init(s), 0);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 1);
+ assert_strcmp(mock_send[0], "AUTHENTICATE EXTERNAL");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 2);
+ assert_strcmp(mock_line[1], "Invalid SASL response for mechanism EXTERNAL: response is null");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 3);
+ assert_strcmp(mock_line[2], "Invalid SASL response for mechanism EXTERNAL: '*'");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE +");
+ assert_eq(irc_recv(s, &m), 0);
+ assert_eq(mock_send_n, 2);
+ assert_eq(mock_line_n, 3);
+ assert_strcmp(mock_send[1], "AUTHENTICATE +");
+}
+
+static void
+test_ircv3_recv_AUTHENTICATE_PLAIN(void)
+{
+ server_set_sasl(s, "plain", NULL, "pass");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 0);
+ assert_eq(mock_line_n, 1);
+ assert_strcmp(mock_line[0], "SASL mechanism PLAIN requires a username");
+
+ server_set_sasl(s, "plain", "user", NULL);
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 0);
+ assert_eq(mock_line_n, 2);
+ assert_strcmp(mock_line[1], "SASL mechanism PLAIN requires a password");
+
+ server_set_sasl(s, "plain", "user", "pass");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 0);
+ assert_eq(mock_line_n, 3);
+ assert_strcmp(mock_line[2], "Invalid SASL state for mechanism PLAIN: 0");
+
+ assert_eq(ircv3_sasl_init(s), 0);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 3);
+ assert_strcmp(mock_send[0], "AUTHENTICATE PLAIN");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 4);
+ assert_strcmp(mock_line[3], "Invalid SASL response for mechanism PLAIN: response is null");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE *");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 1);
+ assert_eq(mock_line_n, 5);
+ assert_strcmp(mock_line[4], "Invalid SASL response for mechanism PLAIN: '*'");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE +");
+ assert_eq(irc_recv(s, &m), 0);
+ assert_eq(mock_send_n, 2);
+ assert_eq(mock_line_n, 5);
+ assert_strcmp(mock_send[1], "AUTHENTICATE dXNlcgB1c2VyAHBhc3M=");
+
+ char user1[150] = {0};
+ char user2[147] = {0};
+
+ memset(user1, 'x', sizeof(user1) - 1);
+ memset(user2, 'x', sizeof(user2) - 1);
+
+ server_set_sasl(s, "plain", user1, "pass");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE +");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 2);
+ assert_eq(mock_line_n, 6);
+ assert_strcmp(mock_line[5], "SASL decoded auth message too long");
+
+ server_set_sasl(s, "plain", user2, "pass");
+
+ IRC_MESSAGE_PARSE("AUTHENTICATE +");
+ assert_eq(irc_recv(s, &m), -1);
+ assert_eq(mock_send_n, 2);
+ assert_eq(mock_line_n, 7);
+ assert_strcmp(mock_line[6], "SASL encoded auth message too long");
+}
+
+static void
+test_ircv3_numeric_900(void)
+{
+ IRC_MESSAGE_PARSE("900