13 Commits

Author SHA1 Message Date
2336340ad8 Bump version, update NEWS 2020-10-31 23:50:32 +01:00
8f5dec0456 degesch: buffer creation cleanup 2020-10-31 23:44:18 +01:00
3dc6ee9a5b degesch: sanitize IRC nicknames/channel names
Don't trust the IRCd to have them in a subset of UTF-8.
2020-10-31 23:25:08 +01:00
821ce04915 degesch: implement autocompletion for /set
It was super annoying to just slightly modify strings and
string arrays, now you can have existing values filled in.

complete_word() looks a bit cleaner now as well.
2020-10-31 23:18:31 +01:00
2fe3b95ecd README.adoc: improve backlog helper invocation
When fancy-prompt.lua is enabled, tho prompt is two-lined
and a simple PageUp would skip one line of content.

It works slightly better than it should: when there's under
a page of content to scroll, there is no shift at all.
2020-10-31 20:00:23 +01:00
32c99c9d66 kike: avoid crash with a wildcard address
A most unfortunate 06d3b3b regression, mostly stemming from
forgetting why the `break` was in place and not documenting it.
2020-10-31 17:34:32 +01:00
cd7133e173 README.adoc: minor documentation update 2020-10-31 16:06:13 +01:00
b4ed52015a degesch: mark some issues for later resolution 2020-10-31 16:06:12 +01:00
271689da99 fancy-prompt.lua: allow non-ASCII buffer names
It may theoretically bite us in the ass with non-UTF-8-compliant
IRC servers, and certainly with double-width characters.
2020-10-31 16:05:15 +01:00
38c23d0d38 degesch: fix fancy-prompt.lua with libedit
Partly by unifying the interface for prompt hooks to match GNU Readline.
2020-10-31 16:04:30 +01:00
439af8884c degesch: make PageUp actually scroll a page up
Now that the input to the backlog helper is wrapped the same way
as what we display.  There's a slight issue always triggered by
fancy-prompt.lua where a multiline prompt/command line makes less(1)
go too high up but it's nothing too important.
2020-10-31 16:00:55 +01:00
8ccf38ad76 Minor rebranding
There's nothing experimental about this project anymore.  It's stable.

Maybe we should add a photo of Hitler or something.
2020-10-31 13:42:56 +01:00
47a4c8beca CMakeLists.txt: clean up OpenBSD support
A few things might have changed.
2020-10-29 15:27:09 +01:00
10 changed files with 176 additions and 76 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0)
project (uirc3 VERSION 1.0.0 LANGUAGES C)
project (uirc3 VERSION 1.1.0 LANGUAGES C)
# Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
@@ -54,8 +54,6 @@ include_directories (${libssl_INCLUDE_DIRS})
link_directories (${libssl_LIBRARY_DIRS})
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
include_directories (/usr/local/include)
link_directories (/usr/local/lib)
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
# our POSIX version macros make it undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
@@ -115,7 +113,7 @@ if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT)
elseif (WANT_READLINE)
# OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (/usr/local/include/ereadline)
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
list (APPEND degesch_libraries ereadline)
else ()
list (APPEND degesch_libraries readline)
@@ -218,7 +216,7 @@ foreach (page ${project_MAN_PAGES})
endforeach ()
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Experimental IRC client, daemon and bot")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unethical IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

9
NEWS
View File

@@ -1,3 +1,12 @@
1.1.0 (2020-10-31) "What Do You Mean By 'This Isn't Germany'?"
* degesch: made fancy-prompt.lua work with libedit
* kike: fixed a regression with an unspecified "bind_host"
* Miscellaneous minor improvements
1.0.0 (2020-10-29) "We're Finally There!"
* Coming with real manual pages instead of help2man-generated stubs

View File

@@ -3,8 +3,8 @@ uirc3
:compact-option:
The [line-through]#unethical# edgy IRC trinity. This project consists of an
experimental IRC client, daemon, and bot. It's all you're ever going to need
for chatting, as long as you can make do with minimalist software.
IRC client, daemon, and bot. It's all you're ever going to need for chatting,
as long as you can make do with minimalist software.
All of them have these potentially interesting properties:
@@ -103,6 +103,7 @@ Or you can try telling CMake to make a package for you. For Debian it is:
Usage
-----
'degesch' has in-program configuration. Just run it and read the instructions.
Consult its link:degesch.adoc[man page] for details about the interface.
For the rest you might want to generate a configuration file:
@@ -124,7 +125,7 @@ as a `forking` type systemd user service.
Client Certificates
-------------------
'kike' uses SHA1 fingerprints of TLS client certificates to authenticate users.
'kike' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
To get the fingerprint from a certificate file in the required form, use:
$ openssl x509 -in public.pem -outform DER | sha1sum
@@ -152,13 +153,13 @@ Beware that you can easily break the program if you're not careful.
How do I make degesch look like the screenshot?
-----------------------------------------------
First of all, you must build it with Lua support. With the defaults, degesch
doesn't look very fancy because some things are rather hackish, and I also don't
want to depend on UTF-8 or 256color terminals in the code. In addition to that,
I appear to be one of the few people who use black on white terminals.
doesn't look too fancy because I don't want to depend on Lua or 256-colour
terminals. In addition to that, I appear to be one of the few people who use
black on white terminals.
/set behaviour.date_change_line = "%a %e %b %Y"
/set behaviour.plugin_autoload += "fancy-prompt.lua"
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb1d -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
/set attributes.userhost = "\x1b[38;5;109m"
/set attributes.join = "\x1b[38;5;108m"
/set attributes.part = "\x1b[38;5;138m"

View File

@@ -6,7 +6,7 @@ degesch(1)
Name
----
degesch - an experimental IRC client
degesch - terminal-based IRC client
Synopsis
--------

168
degesch.c
View File

@@ -1,5 +1,5 @@
/*
* degesch.c: the experimental IRC client
* degesch.c: a terminal-based IRC client
*
* Copyright (c) 2015 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
@@ -1591,12 +1591,14 @@ static struct ispect_field g_buffer_ispect[] =
};
static struct buffer *
buffer_new (struct input *input)
buffer_new (struct input *input, enum buffer_type type, char *name)
{
struct buffer *self = xcalloc (1, sizeof *self);
self->ref_count = 1;
self->input = input;
self->input_data = CALL (input, buffer_new);
self->type = type;
self->name = name;
return self;
}
@@ -2458,7 +2460,7 @@ static struct config_schema g_config_behaviour[] =
{ .name = "backlog_helper",
.comment = "Shell command to display a buffer's history",
.type = CONFIG_ITEM_STRING,
.default_ = "\"LESSSECURE=1 less -M -R +G\"" },
.default_ = "\"LESSSECURE=1 less -M -R +Gb\"" },
{ .name = "backlog_helper_strip_formatting",
.comment = "Strip formatting from backlog helper input",
.type = CONFIG_ITEM_BOOLEAN,
@@ -4446,9 +4448,8 @@ buffer_remove_safe (struct app_context *ctx, struct buffer *buffer)
static void
init_global_buffer (struct app_context *ctx)
{
struct buffer *global = ctx->global_buffer = buffer_new (ctx->input);
global->type = BUFFER_GLOBAL;
global->name = xstrdup (PROGRAM_NAME);
struct buffer *global = ctx->global_buffer =
buffer_new (ctx->input, BUFFER_GLOBAL, xstrdup (PROGRAM_NAME));
buffer_add (ctx, global);
buffer_activate (ctx, global);
@@ -4456,6 +4457,19 @@ init_global_buffer (struct app_context *ctx)
// --- Users, channels ---------------------------------------------------------
static char *
irc_make_buffer_name (struct server *s, const char *target)
{
if (!target)
return xstrdup (s->name);
// XXX: this may be able to trigger the uniqueness assertion with non-UTF-8
char *target_utf8 = irc_to_utf8 (target);
char *result = xstrdup_printf ("%s.%s", s->name, target_utf8);
free (target_utf8);
return result;
}
static void
irc_user_on_destroy (void *object, void *user_data)
{
@@ -4495,9 +4509,8 @@ irc_get_or_make_user_buffer (struct server *s, const char *nickname)
struct user *user = irc_get_or_make_user (s, nickname);
// Open a new buffer for the user
buffer = buffer_new (s->ctx->input);
buffer->type = BUFFER_PM;
buffer->name = xstrdup_printf ("%s.%s", s->name, nickname);
buffer = buffer_new (s->ctx->input,
BUFFER_PM, irc_make_buffer_name (s, nickname));
buffer->server = s;
buffer->user = user;
str_map_set (&s->irc_buffer_map, user->nickname, buffer);
@@ -6028,6 +6041,14 @@ make_prompt (struct app_context *ctx, struct str *output)
static void
input_maybe_set_prompt (struct input *self, char *new_prompt)
{
// Fix libedit's expectations to see a non-control character following
// the end mark (see prompt.c and literal.c) by cleaning this up
for (char *p = new_prompt; *p; )
if (p[0] == INPUT_END_IGNORE && p[1] == INPUT_START_IGNORE)
memmove (p, p + 2, strlen (p + 2) + 1);
else
p++;
// Redisplay can be an expensive operation
const char *prompt = CALL (self, get_prompt);
if (prompt && !strcmp (new_prompt, prompt))
@@ -6055,6 +6076,12 @@ on_refresh_prompt (struct app_context *ctx)
prompt.str[--prompt.len] = 0;
attributed_suffix = " ";
}
// Also enable a uniform interface for prompt hooks by assuming it uses
// GNU Readline escapes: turn this into libedit's almost-flip-flop
for (size_t i = 0; i < prompt.len; i++)
if (prompt.str[i] == '\x01' || prompt.str[i] == '\x02')
prompt.str[i] = INPUT_START_IGNORE /* == INPUT_END_IGNORE */;
#endif // HAVE_EDITLINE
char *localized = iconv_xstrdup (ctx->term_from_utf8, prompt.str, -1, NULL);
@@ -6628,15 +6655,15 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
if (!irc_is_this_us (s, msg->prefix))
return;
buffer = buffer_new (s->ctx->input);
buffer->type = BUFFER_CHANNEL;
buffer->name = xstrdup_printf ("%s.%s", s->name, channel_name);
buffer = buffer_new (s->ctx->input,
BUFFER_CHANNEL, irc_make_buffer_name (s, channel_name));
buffer->server = s;
buffer->channel = channel =
irc_make_channel (s, xstrdup (channel_name));
str_map_set (&s->irc_buffer_map, channel->name, buffer);
buffer_add (s->ctx, buffer);
// XXX: this is annoying, consider only doing it a while after /join
buffer_activate (s->ctx, buffer);
}
@@ -8299,9 +8326,8 @@ server_add (struct app_context *ctx,
s->config = subtree;
// Add a buffer and activate it
struct buffer *buffer = s->buffer = buffer_new (ctx->input);
buffer->type = BUFFER_SERVER;
buffer->name = xstrdup (s->name);
struct buffer *buffer = s->buffer = buffer_new (ctx->input,
BUFFER_SERVER, irc_make_buffer_name (s, NULL));
buffer->server = s;
buffer_add (ctx, buffer);
@@ -12341,15 +12367,12 @@ completion_locate (struct completion *self, size_t offset)
self->location = i - 1;
}
static bool
completion_matches (struct completion *self, int word, const char *pattern)
static char *
completion_word (struct completion *self, int word)
{
hard_assert (word >= 0 && word < (int) self->words_len);
char *text = xstrndup (self->line + self->words[word].start,
return xstrndup (self->line + self->words[word].start,
self->words[word].end - self->words[word].start);
bool result = !fnmatch (pattern, text, 0);
free (text);
return result;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -12444,6 +12467,66 @@ complete_option (struct app_context *ctx, struct completion *data,
strv_free (&options);
}
static void
complete_set_value (struct config_item *item, const char *word,
struct strv *output)
{
struct str serialized = str_make ();
config_item_write (item, false, &serialized);
if (!strncmp (serialized.str, word, strlen (word)))
strv_append_owned (output, str_steal (&serialized));
else
str_free (&serialized);
}
static void
complete_set_value_array (struct config_item *item, const char *word,
struct strv *output)
{
if (!item->schema || item->schema->type != CONFIG_ITEM_STRING_ARRAY)
return;
struct strv items = strv_make ();
cstr_split (item->value.string.str, ",", false, &items);
for (size_t i = 0; i < items.len; i++)
{
struct str wrapped = str_make (), serialized = str_make ();
str_append (&wrapped, items.vector[i]);
config_item_write_string (&serialized, &wrapped);
str_free (&wrapped);
if (!strncmp (serialized.str, word, strlen (word)))
strv_append_owned (output, str_steal (&serialized));
else
str_free (&serialized);
}
strv_free (&items);
}
static void
complete_set (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output)
{
if (data->location == 1)
{
complete_option (ctx, data, word, output);
return;
}
if (data->location != 3)
return;
char *key = completion_word (data, 1);
struct config_item *item = config_item_get (ctx->config.root, key, NULL);
if (item)
{
char *op = completion_word (data, 2);
if (!strcmp (op, "-=")) complete_set_value_array (item, word, output);
if (!strcmp (op, "=")) complete_set_value (item, word, output);
free (op);
}
free (key);
}
static void
complete_topic (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output)
@@ -12497,33 +12580,30 @@ static char **
complete_word (struct app_context *ctx, struct completion *data,
const char *word)
{
// First figure out what exactly we need to complete
bool try_commands = false;
bool try_options = false;
bool try_topic = false;
bool try_nicknames = false;
if (data->location == 0 && completion_matches (data, 0, "/*"))
try_commands = true;
else if (data->location == 1 && completion_matches (data, 0, "/set"))
try_options = true;
else if (data->location == 1 && completion_matches (data, 0, "/help"))
try_commands = try_options = true;
else if (data->location == 1 && completion_matches (data, 0, "/topic"))
try_topic = try_nicknames = true;
else
try_nicknames = true;
char *initial = completion_word (data, 0);
// Start with a placeholder for the longest common prefix
struct strv words = strv_make ();
// Add placeholder
strv_append_owned (&words, NULL);
if (try_commands) complete_command (ctx, data, word, &words);
if (try_options) complete_option (ctx, data, word, &words);
if (try_topic) complete_topic (ctx, data, word, &words);
if (try_nicknames) complete_nicknames (ctx, data, word, &words);
if (data->location == 0 && *initial == '/')
complete_command (ctx, data, word, &words);
else if (data->location >= 1 && !strcmp (initial, "/set"))
complete_set (ctx, data, word, &words);
else if (data->location == 1 && !strcmp (initial, "/help"))
{
complete_command (ctx, data, word, &words);
complete_option (ctx, data, word, &words);
}
else if (data->location == 1 && !strcmp (initial, "/topic"))
{
complete_topic (ctx, data, word, &words);
complete_nicknames (ctx, data, word, &words);
}
else
complete_nicknames (ctx, data, word, &words);
cstr_set (&initial, NULL);
LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
{
struct completion_hook *hook = (struct completion_hook *) iter;
@@ -14064,7 +14144,7 @@ main (int argc, char *argv[])
};
struct opt_handler oh =
opt_handler_make (argc, argv, opts, NULL, "Experimental IRC client.");
opt_handler_make (argc, argv, opts, NULL, "Terminal-based IRC client.");
bool format_mode = false;
int c;

View File

@@ -6,7 +6,7 @@ kike(1)
Name
----
kike - an experimental IRC daemon
kike - IRC daemon
Synopsis
--------

33
kike.c
View File

@@ -1,5 +1,5 @@
/*
* kike.c: the experimental IRC daemon
* kike.c: an IRC daemon
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
@@ -614,7 +614,8 @@ struct server_context
{
int *listen_fds; ///< Listening socket FD's
struct poller_fd *listen_events; ///< New connections available
size_t n_listen_fds; ///< Number of listening sockets
size_t listen_len; ///< Number of listening sockets
size_t listen_alloc; ///< How many we've allocated
time_t started; ///< When has the server been started
@@ -695,7 +696,7 @@ server_context_free (struct server_context *self)
{
str_map_free (&self->config);
for (size_t i = 0; i < self->n_listen_fds; i++)
for (size_t i = 0; i < self->listen_len; i++)
{
poller_fd_reset (&self->listen_events[i]);
xclose (self->listen_fds[i]);
@@ -746,12 +747,12 @@ irc_initiate_quit (struct server_context *ctx)
{
print_status ("shutting down");
for (size_t i = 0; i < ctx->n_listen_fds; i++)
for (size_t i = 0; i < ctx->listen_len; i++)
{
poller_fd_reset (&ctx->listen_events[i]);
xclose (ctx->listen_fds[i]);
}
ctx->n_listen_fds = 0;
ctx->listen_len = 0;
for (struct client *iter = ctx->clients; iter; iter = iter->next)
if (!iter->closing_link)
@@ -3852,16 +3853,19 @@ irc_listen_resolve (struct server_context *ctx,
int fd;
for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
{
if (ctx->listen_len == ctx->listen_alloc)
break;
if ((fd = irc_listen (gai_iter)) == -1)
continue;
set_blocking (fd, false);
struct poller_fd *event = &ctx->listen_events[ctx->n_listen_fds];
struct poller_fd *event = &ctx->listen_events[ctx->listen_len];
*event = poller_fd_make (&ctx->poller, fd);
event->dispatcher = (poller_fd_fn) on_irc_client_available;
event->user_data = ctx;
ctx->listen_fds[ctx->n_listen_fds++] = fd;
ctx->listen_fds[ctx->listen_len++] = fd;
poller_fd_set (event, POLLIN);
}
freeaddrinfo (gai_result);
@@ -3882,13 +3886,20 @@ irc_setup_listen_fds (struct server_context *ctx, struct error **e)
struct strv ports = strv_make ();
cstr_split (bind_port, ",", true, &ports);
ctx->listen_fds = xcalloc (ports.len, sizeof *ctx->listen_fds);
ctx->listen_events = xcalloc (ports.len, sizeof *ctx->listen_events);
// For C and simplicity's sake let's assume that the host will resolve
// to at most two different addresses: IPv4 and IPv6 in case it is NULL
ctx->listen_alloc = ports.len * 2;
ctx->listen_fds =
xcalloc (ctx->listen_alloc, sizeof *ctx->listen_fds);
ctx->listen_events =
xcalloc (ctx->listen_alloc, sizeof *ctx->listen_events);
for (size_t i = 0; i < ports.len; i++)
irc_listen_resolve (ctx, bind_host, ports.vector[i], &gai_hints);
strv_free (&ports);
if (!ctx->n_listen_fds)
if (!ctx->listen_len)
{
error_set (e, "%s: %s",
"network setup failed", "no ports to listen on");
@@ -3991,7 +4002,7 @@ main (int argc, char *argv[])
};
struct opt_handler oh =
opt_handler_make (argc, argv, opts, NULL, "Experimental IRC daemon.");
opt_handler_make (argc, argv, opts, NULL, "IRC daemon.");
int c;
while ((c = opt_handler_get (&oh)) != -1)

View File

@@ -64,12 +64,13 @@ degesch.hook_prompt (function (hook)
local lines, cols = degesch.get_screen_size ()
x = x .. " " .. active .. string.rep (" ", cols)
-- Readline seems to be broken and completely corrupts the prompt
-- (tested on 7.0.003 Archlinux, 7.0-5 Debian buster)
x = x:gsub("[\128-\255]", "?")
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt.
-- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1.
--x = x:gsub("[\128-\255]", "?")
-- Cut off extra characters and apply formatting, including the hack.
-- Note that this doesn't count with full-width or zero-width characters.
-- FIXME: this doesn't count with full-width or zero-width characters.
-- We might want to export wcwidth() above term_from_utf8 somehow.
local overflow = utf8.offset (x, cols - 1)
if overflow then x = x:sub (1, overflow) end
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..

View File

@@ -6,7 +6,7 @@ zyklonb(1)
Name
----
zyklonb - an experimental IRC bot
zyklonb - modular IRC bot
Synopsis
--------

View File

@@ -1,5 +1,5 @@
/*
* zyklonb.c: the experimental IRC bot
* zyklonb.c: a modular IRC bot
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
@@ -1983,7 +1983,7 @@ main (int argc, char *argv[])
};
struct opt_handler oh =
opt_handler_make (argc, argv, opts, NULL, "Experimental IRC bot.");
opt_handler_make (argc, argv, opts, NULL, "Modular IRC bot.");
int c;
while ((c = opt_handler_get (&oh)) != -1)