215 Commits

Author SHA1 Message Date
fa78831cbd Update NEWS, bump version 2018-06-22 00:59:41 +02:00
94b0ec80cf fancy-prompt.lua: workaround a Readline UTF-8 bug 2018-06-22 00:55:17 +02:00
300f9a9708 Bump liberty 2018-06-22 00:03:21 +02:00
b1a89f313a degesch: add static analysis for the logger
Caught two more occurences than I was able to find by just scanning
the source, so the effort wasn't in vain.
2018-06-21 23:46:03 +02:00
fab5115cd0 Remove .travis.yml
We don't depend on any proprietary services no longer.  I'll have to
make my own replacements with blackjack and hookers.  Until then,
the file stays in the commit log as an example.
2018-06-21 23:46:03 +02:00
d0cb3c1ac6 Update README 2018-06-21 23:46:03 +02:00
a0e9ede3e3 Relicense to 0BSD, update mail address
I've come to the conclusion that copyright mostly just stands in the way
of software development.  In my jurisdiction I cannot give up my own
copyright and 0BSD seems to be the closest thing to public domain.

The updated mail address, also used in my author/committer lines,
is shorter and looks nicer.  People rarely interact anyway.
2018-06-21 23:46:03 +02:00
787569e653 Update submodule URL for liberty 2018-06-21 23:45:55 +02:00
5d353b0721 Prepare NEWS for the next release 2018-06-21 23:26:16 +02:00
006d34eeae degesch: fix some log messages
We could use some static analysis for these.
2018-06-21 23:25:45 +02:00
19400ee8b7 kike: disable TLS session reuse 2018-01-09 06:25:16 +01:00
674ffb2f6d kike: handle accept() errors better
Might prevent some denial of service attacks.
2018-01-09 05:48:36 +01:00
6c30452b28 kike: thorough review, no functional changes 2018-01-09 05:47:37 +01:00
670e1c5770 kike: introduce cstr_set() 2018-01-08 23:16:14 +01:00
4586b0e1e4 degesch: introduce cstr_set() 2018-01-08 22:19:28 +01:00
b4507b56af degesch: thorough review, no functional changes 2018-01-08 22:19:23 +01:00
bf6d507bb2 degesch: fix IPv6:port in irc_split_host_port() 2018-01-08 22:19:02 +01:00
099a49e6d5 degesch: fix a minor bug in buffer_merge()
The pointer to the last item in the linked list wasn't always fixed,
although nothing really touched it afterwards.
2018-01-08 22:17:02 +01:00
4627ee82dd degesch: simplify a popular assertion 2018-01-08 22:16:57 +01:00
682f90e989 degesch: simplify the configuration dumper 2018-01-08 22:16:52 +01:00
277af83100 degesch: show an error message on log write failure
Running out of space and I/O errors seem like the most likely causes.
2018-01-08 22:16:36 +01:00
a5a0078def degesch: make buffer index computation easier to follow 2018-01-08 22:16:10 +01:00
868e34d15c degesch: fix a Lua error message 2018-01-08 22:16:06 +01:00
dc47b16034 Bump liberty, avoid fall-through warnings 2018-01-07 17:37:19 +01:00
d0f19f8be3 Update README
"Edgy" is actually a fitting word.  And we've lost OBS!
2017-12-06 23:49:18 +01:00
ddb45a1cc4 Update README 2017-12-02 13:06:39 +01:00
3974919741 Update README
So be it, SJWHub, at least I have a reason to move.
2017-12-02 11:06:48 +01:00
36be830bfc degesch: better shift state encoding handling
I don't know, probably didn't matter.
2017-07-07 20:55:25 +02:00
f7dce5e861 slack.lua: add a feature to undo emoji 2017-07-03 06:45:46 +02:00
757047bd20 CMakeLists.txt: fix variable name 2017-07-03 06:44:15 +02:00
a2611cdc3c Rework constructors/destructors 2017-06-22 22:56:24 +02:00
68bc297809 Bump liberty 2017-06-22 22:39:39 +02:00
933760c2a2 kike: fix two memory leaks 2017-06-22 20:36:21 +02:00
156ea32a90 slack.lua: support @here and @channel 2017-05-18 10:44:36 +02:00
f744681b17 slack.lua: improve input hook matching 2017-05-17 15:05:51 +02:00
bdc6334aec slack.lua: more unfucking
And now it's already fairly usable.
2017-05-17 00:32:54 +02:00
96864517c6 Fix licensing notice in README 2017-05-14 22:13:00 +02:00
0bdcd4aa8b fancy-prompt.lua: remove unnecessary local variable 2017-05-13 20:04:21 +02:00
b18a8048c1 degesch: add a slack plugin
Slack's IRC gateway is crap but it doesn't need to be *such* crap.
2017-05-13 20:04:21 +02:00
c3d62b8799 Avoid the "poller_fd::closed" feature
Reliability enhancement for Linux.

This feature was created for ponymap, however we don't care about an
extra syscall in most places.  Doing it right even saves lines.
2017-05-06 21:35:44 +02:00
ec842db0fb Update copyright years 2017-04-22 19:41:27 +02:00
0981df485a degesch: simplify quitting
- send a QUIT on C-c, too
 - shut down the connection on /disconnect, too

Connection management is one of the few fucked up parts
that remain in that state for historical reasons.
2017-04-20 20:55:49 +02:00
9f0c18cc41 degesch: fix confusing message
It seemed like we were connecting albeit we were connected already.
2017-04-20 20:26:04 +02:00
1313a712df degesch: make a second /disconnect always succeed 2017-04-20 20:25:21 +02:00
f45f9ab873 Travis CI: brevify notifications 2017-02-03 23:17:15 +01:00
9e5725662f Bump liberty 2017-01-23 23:50:27 +01:00
0785a6f417 degesch: Lua is no longer experimental
But rather essential to me.
2017-01-23 23:41:14 +01:00
cb9957cd64 Travis CI: try adding the PPA back 2016-12-30 14:47:50 +01:00
40bb2497f7 Travis CI: try removing a dead PPA 2016-12-30 14:42:06 +01:00
d7960b463f Fix LibreSSL compatibility 2016-12-30 08:51:49 +01:00
3c048f0d56 Bump version 2016-12-30 08:15:44 +01:00
8e668ff31a Various fixes related to channel modes
Bugs unnoticed for so long.
2016-12-30 08:08:34 +01:00
eb70bf3fbc Cleanup 2016-12-28 12:44:27 +01:00
d86a68f510 Add support for OpenSSL 1.1.0 2016-12-28 12:40:47 +01:00
d6be22291d degesch: /query w/o arguments just opens the query 2016-12-06 13:51:16 +01:00
a813babb89 fancy-prompt.lua: fix parametrized modes 2016-12-02 12:28:55 +01:00
b666ce6926 fancy-prompt.lua: change background on highlight 2016-12-02 12:28:55 +01:00
e2bb051bd3 degesch: replace degesch.connect with async.dial
Halfway there, looks much saner.
2016-11-04 22:02:26 +01:00
52d1ded7df degesch: move the Lua async code within the file 2016-11-04 20:44:23 +01:00
cb9f187f80 degesch: get rid of Lua timer hooks
Since they were the exception and have been replaced with the async API.
2016-11-04 20:21:46 +01:00
0247c4667a degesch: Lua coroutine safety 2016-11-04 20:12:28 +01:00
572f4e2ea3 degesch: implement Lua coroutine async basics 2016-11-04 20:11:59 +01:00
50599e09bd Update README, add a screenshot for degesch 2016-10-30 18:52:20 +01:00
b24bb0aded degesch: fix join/part hiding in the backlog 2016-10-30 16:24:23 +01:00
7c6cf42075 thin-cursor.lua: update comments 2016-10-30 01:50:21 +02:00
414a525c4d degesch: add a thin-cursor plugin 2016-10-30 00:00:48 +02:00
6cee7159f2 degesch: clean up
Caught by Coverity, however it is quite harmless.
2016-10-29 21:08:15 +02:00
568f9b7123 degesch: tiny fixes for the prompt hook
It should return valid UTF-8.

Also remember to refresh the prompt upon hook removal.
2016-10-29 20:03:31 +02:00
0d499dd125 degesch: avoid senseless indirection in hooks
It's always been one function call only this far.
2016-10-29 19:51:54 +02:00
37e49b54cf degesch: rename things around terminal attributes 2016-10-29 18:07:28 +02:00
742d590b8d degesch: simplify "attribute_printer"
Now that the line wrapper took over some of the state.
2016-10-29 17:53:06 +02:00
b6528c73e3 degesch: microoptimization 2016-10-28 18:16:21 +02:00
1e79aaec26 degesch: refresh the prompt when a hook is set 2016-10-28 13:58:37 +02:00
0995da3900 degesch: don't consider all mode changes important 2016-10-28 13:32:29 +02:00
c8a826f016 degesch: optimize Lua weak refs 2016-10-28 13:09:50 +02:00
95c7ababc3 degesch: add a "fancy-prompt" plugin
So that the client looks at least a tiny bit decent if needed.
2016-10-28 12:53:18 +02:00
a0d733fdb9 Update NEWS, README 2016-10-28 12:47:11 +02:00
557a39c6c8 degesch: export server state as a string to Lua 2016-10-28 12:47:11 +02:00
745e758394 degesch: add Lua API for screen size retrieval 2016-10-28 04:12:06 +02:00
b60bdf119a degesch: add a prompt hook 2016-10-28 04:12:06 +02:00
278e2b236b degesch: add introspection for refs within str_maps
This required some fixes to the design.
2016-10-28 04:12:05 +02:00
2f758bbdb9 degesch: allow lists of refs in introspection 2016-10-28 04:12:05 +02:00
911276b263 degesch: add introspection for "app_context" 2016-10-28 04:12:05 +02:00
cb5ad675a6 degesch: add introspection for "str" and "str_map" 2016-10-28 04:12:05 +02:00
9408dfc67c degesch: create Lua refs through introspection 2016-10-28 04:12:05 +02:00
fed8b06aff degesch: begin work on direct introspection 2016-10-28 04:12:05 +02:00
7e64fd9886 degesch: cleanup 2016-10-28 04:12:05 +02:00
6928184a3d degesch: defer prompt refreshing
Now that we do it each time we receive a message from the server.
2016-10-23 17:34:52 +02:00
f7155f3919 degesch: allow hiding join/part messages 2016-10-23 17:14:24 +02:00
f032466307 degesch: comments, no functional change 2016-10-23 17:14:24 +02:00
c0f4b554ef degesch: show channel user count in the status 2016-10-23 17:14:24 +02:00
639da7a9a7 degesch: accept Word shortcuts for formatting
Because why not.
2016-10-23 13:40:04 +02:00
230b04014f Bump liberty, add consts to some arguments 2016-10-23 13:38:46 +02:00
4848354bb9 Get rid of the remaining FAILs 2016-10-11 12:05:17 +02:00
8028c7fa47 Bump liberty 2016-10-11 10:52:49 +02:00
43de836b91 degesch: exit with error when arguments are given 2016-09-29 13:40:15 +02:00
16d10f574b degesch: simplify highlight detection 2016-09-25 14:11:30 +02:00
4cefa5ab1b degesch: fix highlight detection in colored text 2016-09-23 23:46:26 +02:00
92a4d4b5a7 Better support for the KILL command 2016-09-23 22:50:30 +02:00
26f94d2459 degesch: add a "censor" plugin
So far this approach screws up highlights, which is actually a bug.
2016-09-23 18:59:37 +02:00
0be43691d0 Update README 2016-07-23 20:29:25 +02:00
483ab39e3c degesch: die on configuration parse errors
Seems more sensible.
2016-07-23 20:00:40 +02:00
beaf1a1f82 degesch: fix Ctrl-J in Readline 2016-07-23 19:13:55 +02:00
5613c326c9 degesch: fix CTCP handling
In `/me :\` practically no client bothers to escape the backslash but we
used to interpret it as the start of an escape sequence anyway.

Silly us, no one respects any standards.
2016-07-09 22:55:26 +02:00
db17223df0 Bump version; update NEWS, README 2016-04-28 23:46:08 +02:00
2474b5f3f5 calc: fix usage of (substring) 2016-04-28 23:25:29 +02:00
d97f28e7f7 ZyklonB: add a seen plugin 2016-04-24 21:05:53 +02:00
d6a9e1dca1 degesch: customizable date change messages
Now also in the backlog.
2016-04-21 23:50:05 +02:00
c8e4833086 degesch: add a NOWRAP flag to formatter_flush()
--format should work as before now.

It is now also possible to rebind PageUp to show a wrapped backlog.
2016-04-21 23:50:05 +02:00
99595c0d81 degesch: update comments 2016-04-21 23:50:05 +02:00
75c4645f10 degesch: add an auto-rejoin.lua plugin 2016-04-21 22:12:33 +02:00
fa5e005728 degesch: refactor Lua weak objects 2016-04-21 22:09:35 +02:00
a9b77b3206 degesch: expose channels and users to Lua 2016-04-21 22:09:35 +02:00
29418e5e55 ping-timeout.lua: fix message parsing 2016-04-21 22:09:35 +02:00
4665807d09 degesch: expose message parsing to Lua 2016-04-21 22:09:35 +02:00
1180255e7b calc: comment updates, import fixes 2016-04-20 22:55:40 +02:00
6f85490fa3 Update NEWS 2016-04-16 20:11:11 +02:00
e97c60245c ZyklonB: add a calc plugin 2016-04-16 20:11:11 +02:00
3a8d70de66 degesch: fix crash on invalid cp1252 characters
We don't even really need iconv here.
2016-04-03 04:05:04 +02:00
695d615225 ZyklonB, kike: Use pledge(2) in OpenBSD
degesch has something like "stdio wpath cpath inet tty proc exec"
but given that it's user-extensible and very annoying for users to
have it crash, I'm leaving it unrestricted for now.
2016-03-30 00:50:44 +02:00
8a3144f0ac degesch: update program logo
I've noticed that the old one wasn't very pleasant to look at.
2016-03-28 21:08:04 +02:00
48423aa4af Update README 2016-03-28 21:07:56 +02:00
11a6c7662e Update NEWS, bump release 2016-03-27 17:17:15 +02:00
dc71af9c31 degesch: fix two minor issues
- completely new unread markers could be created in active buffers
 - control characters confused word wrapping
2016-03-26 20:59:37 +01:00
f964495d1a degesch: don't wrap lines in pager 2016-03-26 16:41:55 +01:00
550a0419a6 degesch: detect //TRANSLIT support, use cp1252
Now BSDs should have it enabled as well.
2016-03-26 14:27:59 +01:00
9b12c830d1 degesch: remap goto-activity to M-a
weechat appears to use this key binding, so let's not reinvent it.
2016-03-26 13:11:28 +01:00
1e24d1d1b8 degesch: add partial matching /buffer goto 2016-03-26 13:00:10 +01:00
6292114c76 degesch: append message count in /buffer listing 2016-03-26 05:15:11 +01:00
e646afe5ae degesch: retain the unseen marker
Don't erase the message counts immediately.

Also make the marker visible in the pager.
2016-03-26 04:55:46 +01:00
410bcdcd78 degesch: phase 1 of word wrapping implementation 2016-03-26 04:52:05 +01:00
62962dc7ac Fix Travis CI notifications 2016-03-14 20:41:07 +01:00
a83ef111c8 Fix git commit tracking 2016-03-13 17:07:04 +01:00
90842c23a2 kike: fix daemonization on *BSD
Bump liberty.
2016-03-13 16:44:58 +01:00
1c9de9291b degesch: cleanup, fix no-tty mode FWIW
Removed the no color mode that couldn't even be enabled.

Not sure why we still support running without a proper terminal
but let's at least not make it crash for now.
2016-03-13 16:44:06 +01:00
e11ca7cc00 Use TMPDIR as a fallback if set 2016-03-13 00:59:28 +01:00
df395f32e5 Update NEWS, README 2016-03-12 23:02:59 +01:00
f96fa66168 degesch: add a --format switch 2016-03-12 14:28:17 +01:00
781a37c152 Don't link kike, ZyklonB against degesch libs 2016-03-10 22:27:09 +01:00
5a197162bf Fix manpage generation
The manpages could end up in a wrong directory.
2016-03-10 20:26:35 +01:00
d70f156a20 Update README 2016-03-10 20:25:06 +01:00
42d88f87f5 degesch: add unbound commands for buffer movement 2016-03-10 00:07:59 +01:00
a1c4a1ef3a degesch: fix binding to our own fns from inputrc 2016-03-10 00:06:28 +01:00
dc248b8840 degesch: add goto activity and highlight 2016-03-08 22:29:40 +01:00
09c7d9a65d degesch: fix mIRC color parsing 2016-03-08 01:59:51 +01:00
0f1fd2eb3a Update NEWS, cleanup 2016-03-07 23:43:47 +01:00
696273558e degesch: rewrite input layer
Now with less #ifdefs.
2016-03-07 22:52:56 +01:00
584d2f0295 degesch: use libffi to unify input callbacks
And fuck you both, Readline and Editline.
2016-03-06 18:12:12 +01:00
3304b718aa Try to use version information from git 2016-03-06 03:52:39 +01:00
10bdf90fe2 Don't force override compile flags 2016-03-06 03:50:35 +01:00
17804fa49b degesch: fix +=/-= to null config items 2016-03-05 19:15:40 +01:00
4b10ea7ab0 factoids: safer DB writes 2016-02-29 03:15:44 +01:00
fb0b0c4cf0 factoids: allow querying definitions by number 2016-02-29 03:11:33 +01:00
f492592735 factoids: look for duplicates case-insensitively 2016-02-29 02:50:53 +01:00
6190733079 degesch: mark a problem 2016-02-29 02:50:41 +01:00
676e6c20fa ZyklonB: add a factoids plugin 2016-02-27 22:29:19 +01:00
ed20322e5e ZyklonB: run plugins in a special work directory
Also small refactoring.
2016-02-20 00:37:57 +01:00
a275f9636c ZyklonB: revisit error handling 2016-02-20 00:01:54 +01:00
056e0a4765 Resolve tls_ca_{file,path} relative to config dir 2016-02-19 23:46:44 +01:00
798ed73a8c ZyklonB: fix segfault on total SOCKS failure 2016-02-12 04:11:33 +01:00
7be995f74a ZyklonB: avoid infinite reexec loops 2016-02-12 04:05:09 +01:00
06b03d336e degesch: fix segfault on sent messages w/o buffer 2016-02-12 04:04:35 +01:00
11519ee860 degesch: update screen size when terminal resumed
Apparently we don't receive the events when we give up the terminal.
2016-02-10 23:02:33 +01:00
03d5b27398 degesch: use mkstemp() with a safe umask 2016-02-09 13:52:56 +01:00
3315b16f79 degesch: log messages from /quote and plugins
That is, parse back all output messages and log based on that.
2016-02-09 05:10:41 +01:00
0c19a384f1 Fix typos 2016-02-09 04:50:51 +01:00
333ad2c981 degesch: allow changing the list of used CAPs
Bump liberty.
2016-02-01 21:57:43 +01:00
a850ee45f1 degesch: optimize buffer memory usage
We have approximately 5 formatter_items per buffer_line.  Let's assume
we're on a 64-bit machine.  Then there were (5 * 2) + 3 useless pointers
(104 bytes) as well as 5 * (4 + 4) = 40 bytes of wasted space because
of needless padding.  That's 144 bytes already.  Compared to that, this
change adds 16 bytes of overhead for an array sentinel, i.e. 128B less.

With a limit of 1000 lines per buffer, we've saved ~128kB per buffer
on completely useless data, and code complexity stays roughly the same.

All in all, memory usage for buffers should be about 50% lower.
2016-01-31 21:43:23 +01:00
10a264ec3d kike: add support for IRCv3.2 server-time 2016-01-31 21:43:23 +01:00
2ec6258ff3 last-fm.lua: don't use empty album names 2016-01-18 01:28:53 +01:00
f57664ddd0 degesch: add an assertion
It should never fail with current code.
2016-01-18 00:59:43 +01:00
773d14e740 degesch: disable TLS compression 2016-01-18 00:45:20 +01:00
221ae03b5c degesch: Lua: fix memory leak on load failure 2016-01-17 22:57:16 +01:00
588a696c68 degesch: lesser heap fragmentation 2016-01-17 22:15:48 +01:00
6db40c4503 Bump liberty 2016-01-17 04:43:43 +01:00
f070523085 Bump liberty 2016-01-16 06:30:08 +01:00
dac5c9df6d kike: more cleanup 2016-01-16 06:30:08 +01:00
ced2a57cfc kike: allow messages before protocol establishment
We can just queue them.
2016-01-16 06:30:08 +01:00
f36d66b0cb kike: asynchronous address resolution
As well as some refactoring and cleanup.
2016-01-16 06:30:08 +01:00
fdeb550ee0 degesch: fix backlog limit
It was effectively infinite.
2016-01-15 22:11:05 +01:00
c4a18ec8a7 degesch: fix and simplify screen handling
Now with less madness.
2016-01-15 05:40:20 +01:00
d0db1a6cdc degesch: enforce fullscreen buffers
Probably long overdue.

Now we actually look like something resembling a regular IRC client.
2016-01-15 05:12:03 +01:00
9333081178 degesch: option for fullscreen buffers 2016-01-15 05:09:42 +01:00
b7c9e8ca23 degesch: make backlog limit configurable 2016-01-15 05:09:42 +01:00
f39e2a4bc8 degesch: Lua: add autocomplete hooks 2016-01-15 02:39:10 +01:00
91f3bd60df degesch: Lua: finish the last-fm plugin 2016-01-14 04:13:03 +01:00
56858a97dd degesch: Lua: allow simulating user input
Also added UTF-8 validation to buffer:log() while I'm at it.
2016-01-14 03:34:29 +01:00
331d1842b9 Bump liberty, shuffle some code 2016-01-14 03:26:02 +01:00
19b09a8cec degesch: add a last-fm "now playing" plugin 2016-01-09 10:27:01 +01:00
32f719dec7 degesch: Lua: pass hostname to on_connected 2016-01-09 06:19:54 +01:00
0b92e9210c degesch: Lua: set sockets to nonblocking 2016-01-09 05:47:24 +01:00
092e9b5101 Bump liberty 2016-01-09 05:27:45 +01:00
faa0c989f8 degesch: Lua: actually allow filtering out input 2016-01-09 05:05:46 +01:00
53e72dd12d degesch: Lua: provide a traceback on load error 2016-01-09 05:01:50 +01:00
83c14ba264 degesch: Lua: fix plugin configuration names 2016-01-09 05:01:50 +01:00
64143a5957 degesch: Lua: fix luaL_ref() usage 2016-01-09 05:01:50 +01:00
aca153f575 degesch: Lua: fix configuration loading
Not the cleanest solution but it has to do for now.
2016-01-09 05:01:50 +01:00
79f46752d4 degesch: make sure newlines are output correctly 2016-01-08 08:40:40 +01:00
2a180ee084 degesch: Lua: finish implementation of connection 2016-01-07 22:49:53 +01:00
6754c59890 degesch: Lua: avoid resource leak
If a connector's on_success callback fails, we need to destroy the connection.
2016-01-07 22:49:53 +01:00
376bbea249 Factor out socket_io_try_{read,write}()
To be reused in Lua connection API.
2016-01-07 22:49:53 +01:00
a5ac0d24b8 degesch: fix handling of input editor death 2016-01-07 22:49:53 +01:00
cabab5f351 Fix a memory leak in SOCKS connector 2016-01-07 22:49:49 +01:00
1d3910fd8e degesch: fix switching of buffers by command
Readline used to erase the new buffer's contents.

Defer processing.
2016-01-07 22:49:49 +01:00
a259e96405 degesch: Lua: fix a resource leak 2016-01-06 00:23:54 +01:00
a7be2bf160 degesch: refactor Lua
And fix handling of nil returns from filter callbacks.
2016-01-05 23:19:28 +01:00
e1c7b8dcaf degesch: Lua: halfplement a connector wrapper
You can't do anything reasonable with the socket now.
2016-01-05 22:12:22 +01:00
00a1bdc707 Fix build of tests 2016-01-05 21:51:07 +01:00
e9b39a1ef7 degesch: Lua: allow arbitrary userdata properties 2016-01-04 23:14:38 +01:00
a227060383 degesch: Lua: use references for hook callbacks
Don't associate the callback with the full userdata object,
we'll need this for something else.
2016-01-04 22:24:05 +01:00
4832a99461 degesch: add basic autocomplete for /topic 2016-01-04 22:06:29 +01:00
0092c34568 Cleanup 2016-01-04 01:15:42 +01:00
aeb047260f Bump liberty, enable TLS SNI
Involves some rewrites to fit the new APIs.

SNI has been implemented Mostly just because we can, I don't think it's
widely in use and kike doesn't support this feature of the protocol either.
2016-01-04 01:12:42 +01:00
28fec6d4a6 ZyklonB: fix tls_ca_{path,file} config. options 2016-01-01 02:00:02 +01:00
1a73f1f1d7 degesch: fix a memory leak under libedit 2016-01-01 02:00:02 +01:00
27 changed files with 6425 additions and 2954 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "liberty"]
path = liberty
url = git://github.com/pjanouch/liberty.git
url = https://git.janouch.name/p/liberty.git

View File

@@ -1,44 +0,0 @@
sudo: required
dist: trusty
language: c
notifications:
irc:
channels: "anathema.irc.so#anathema"
use_notice: true
skip_join: true
env:
global:
- secure: "ck6keK5tTbVCN7VGyKglS890hjovUNt2zyOydiyFtQDciaB/rvEwkKy4anMCEdZHFpGAPE9iBmNYaGUsD1Y+KifhhImVMbuThe2D8MLv5crSLRheYPbbmhO8MWPAxmQnuQhpwsUKZlHvUfX8nh+d0juNdqXklvhVml78Gi99QFw="
matrix:
- readline=ON libedit=OFF
- readline=OFF libedit=ON
addons:
coverity_scan:
project:
name: "pjanouch/uirc3"
description: "Experimental IRC client, daemon and bot"
notification_email: p.janouch@gmail.com
build_command_prepend: "cmake .. -DCMAKE_BUILD_TYPE=Release"
build_command: "make"
branch_pattern: coverity_scan
compiler:
- clang
- gcc
before_install:
# We need this PPA for a recent version of libedit
- sudo add-apt-repository ppa:ondrej/php5-5.6 -y
# We need this PPA for Lua 5.3
- sudo add-apt-repository ppa:vbernat/haproxy-1.6 -y
- sudo apt-get update -qq
install:
- sudo apt-get install -y libncursesw5-dev libreadline-dev libedit-dev
liblua5.3-dev help2man expect
before_script:
- mkdir build
- cd build
script:
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr
-DWANT_READLINE=$readline -DWANT_LIBEDIT=$libedit
- make all test
- cpack -G DEB
- ../test

View File

@@ -6,67 +6,103 @@ option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -Wno-unused-function")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
set (wdisabled "-Wno-unused-function")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# Version
set (project_VERSION_MAJOR "0")
set (project_VERSION_MINOR "9")
set (project_VERSION_PATCH "2")
set (project_version "0.9.6")
set (project_VERSION "${project_VERSION_MAJOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
# Try to append commit ID if it follows a version tag. It might be nicer if
# we could also detect dirty worktrees but that's very hard to get right.
find_package (Git)
set (git_head "${PROJECT_SOURCE_DIR}/.git/HEAD")
if (GIT_FOUND AND EXISTS "${git_head}")
configure_file ("${git_head}" git-head.tag COPYONLY)
file (READ "${git_head}" git_head_content)
if (git_head_content MATCHES "^ref: ([^\r\n]+)")
set (git_ref "${PROJECT_SOURCE_DIR}/.git/${CMAKE_MATCH_1}")
if (EXISTS "${git_ref}")
configure_file ("${git_ref}" git-ref.tag COPYONLY)
endif (EXISTS "${git_ref}")
endif (git_head_content MATCHES "^ref: ([^\r\n]+)")
execute_process (COMMAND ${GIT_EXECUTABLE} describe --tags --match v*
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE git_describe_result
OUTPUT_VARIABLE git_describe OUTPUT_STRIP_TRAILING_WHITESPACE)
if (NOT git_describe_result)
string (REGEX REPLACE "^v" "" project_version "${git_describe}")
endif (NOT git_describe_result)
endif (GIT_FOUND AND EXISTS "${git_head}")
# Dashes make filenames confusing and upset packaging software
string (REPLACE "-" "+" project_version_safe "${project_version}")
# Dependencies
find_package (Curses)
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
include (AddThreads)
find_package (PkgConfig REQUIRED)
pkg_check_modules (libssl REQUIRED libssl libcrypto)
pkg_check_modules (ncursesw ncursesw)
list (APPEND project_libraries ${libssl_LIBRARIES})
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)
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)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
list (APPEND project_libraries ${libssl_LIBRARIES})
include_directories (${libssl_INCLUDE_DIRS})
link_directories (${libssl_LIBRARY_DIRS})
# -lrt is only for glibc < 2.17
# -liconv may or may not be a part of libc
foreach (extra iconv rt)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND project_libraries ${extra_lib_${extra}})
endif (extra_lib_${extra})
endforeach (extra)
include (CheckCSourceRuns)
set (CMAKE_REQUIRED_LIBRARIES ${project_libraries})
get_property (CMAKE_REQUIRED_INCLUDES
DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY INCLUDE_DIRECTORIES)
CHECK_C_SOURCE_RUNS ("#include <iconv.h>
int main () { return iconv_open (\"UTF-8//TRANSLIT\", \"ISO-8859-1\")
== (iconv_t) -1; }" ICONV_ACCEPTS_TRANSLIT)
# Dependencies for degesch
pkg_check_modules (libffi REQUIRED libffi)
list (APPEND degesch_libraries ${libffi_LIBRARIES})
include_directories (${libffi_INCLUDE_DIRS})
link_directories (${libffi_LIBRARY_DIRS})
# FIXME: other Lua versions may be acceptable, don't know yet
pkg_search_module (lua lua53 lua5.3 lua-5.3 lua>=5.3)
option (WITH_LUA "Enable experimental support for Lua plugins" ${lua_FOUND})
option (WITH_LUA "Enable support for Lua plugins" ${lua_FOUND})
if (WITH_LUA)
if (NOT lua_FOUND)
message (FATAL_ERROR "Lua library not found")
endif (NOT lua_FOUND)
list (APPEND project_libraries ${lua_LIBRARIES})
list (APPEND degesch_libraries ${lua_LIBRARIES})
include_directories (${lua_INCLUDE_DIRS})
link_directories (${lua_LIBRARY_DIRS})
endif (WITH_LUA)
# -lpthread is only there for debugging (gdb & errno)
# -lrt is only for glibc < 2.17
# -liconv may or may not be a part of libc
foreach (extra iconv rt pthread)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND project_libraries ${extra})
endif (extra_lib_${extra})
endforeach (extra)
find_package (Curses)
pkg_check_modules (ncursesw ncursesw)
if (ncursesw_FOUND)
list (APPEND project_libraries ${ncursesw_LIBRARIES})
list (APPEND degesch_libraries ${ncursesw_LIBRARIES})
include_directories (${ncursesw_INCLUDE_DIRS})
elseif (CURSES_FOUND)
list (APPEND project_libraries ${CURSES_LIBRARY})
list (APPEND degesch_libraries ${CURSES_LIBRARY})
include_directories (${CURSES_INCLUDE_DIR})
else (CURSES_FOUND)
message (SEND_ERROR "Curses not found")
@@ -78,13 +114,13 @@ elseif (WANT_READLINE)
# OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (/usr/local/include/ereadline)
list (APPEND project_libraries ereadline)
list (APPEND degesch_libraries ereadline)
else ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
list (APPEND project_libraries readline)
list (APPEND degesch_libraries readline)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
elseif (WANT_LIBEDIT)
pkg_check_modules (libedit REQUIRED libedit)
list (APPEND project_libraries ${libedit_LIBRARIES})
list (APPEND degesch_libraries ${libedit_LIBRARIES})
include_directories (${libedit_INCLUDE_DIRS})
endif ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
@@ -114,29 +150,38 @@ set_source_files_properties (${PROJECT_BINARY_DIR}/kike-replies.c
# Build
add_executable (zyklonb zyklonb.c ${common_sources} ${common_headers})
target_link_libraries (zyklonb ${project_libraries})
add_threads (zyklonb)
add_executable (degesch degesch.c kike-replies.c
${common_sources} ${common_headers})
target_link_libraries (degesch ${project_libraries})
target_link_libraries (degesch ${project_libraries} ${degesch_libraries})
add_threads (degesch)
add_executable (kike kike.c kike-replies.c ${common_sources} ${common_headers})
target_link_libraries (kike ${project_libraries})
add_threads (kike)
# Tests
function (make_tests_for target_name)
get_target_property (sources ${target_name} SOURCES)
get_target_property (libraries ${target_name} LINK_LIBRARIES)
get_target_property (options ${target_name} COMPILE_OPTIONS)
set (test test-${target_name})
add_executable (${test} ${sources})
target_link_libraries (${test} ${libraries})
set_target_properties (${test} PROPERTIES
COMPILE_DEFINITIONS TESTING
COMPILE_OPTIONS "${options}")
add_test (NAME ${test} COMMAND ${test})
set_target_properties (${test} PROPERTIES COMPILE_DEFINITIONS TESTING)
endfunction (make_tests_for)
include (CTest)
if (BUILD_TESTING)
make_tests_for (degesch)
add_test (NAME custom-static-analysis
COMMAND ${PROJECT_SOURCE_DIR}/test-static)
endif (BUILD_TESTING)
# Various clang-based diagnostics, loads of fake positives and spam
@@ -180,26 +225,26 @@ endforeach (page)
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
foreach (page ${project_MAN_PAGES})
string (REGEX MATCH "\\.([0-9])" manpage_suffix "${page}")
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
install (FILES "${page}"
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
endforeach (page)
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Experimental IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION ${project_VERSION})
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p.janouch@gmail.com>")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p@janouch.name>")
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set (CPACK_GENERATOR "TGZ;ZIP")
set (CPACK_PACKAGE_FILE_NAME
"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}")
"${PROJECT_NAME}-${project_version_safe}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_version_safe}")
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_version_safe}")
set (CPACK_SET_DESTDIR TRUE)
include (CPack)

View File

@@ -1,8 +1,7 @@
Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
Copyright (c) 2014 - 2018, Přemysl Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

114
NEWS
View File

@@ -1,3 +1,117 @@
0.9.6 (2018-06-22) "I've Been Sitting Here All This Time"
* Code has been relicensed to 0BSD and moved to a private git hosting
* Fix LibreSSL compatibility
* degesch: a second /disconnect cuts the connection by force
* degesch: send a QUIT message to the IRC server on Ctrl-C
* degesch: add a Slack plugin (even though the gateway's now defunct)
* degesch: show an error message on log write failure
* degesch: fix parsing of literal IPv6 addresses with port numbers
* degesch: fix some error messages
* degesch: workaround a Readline bug in the fancy-prompt.lua plugin
* kike: fix two memory leaks
* kike: improve error handling for incoming connections
* kike: disable TLS session reuse
0.9.5 (2016-12-30) "It's Time"
* Better support for the KILL command
* degesch: export many more fields to the Lua API, add a prompt hook
* degesch: show channel user count in the prompt
* degesch: allow hiding join/part messages and other noise (Meta-Shift-H)
* degesch: allow autojoining channels with keys
* degesch: rejoin channels with keys on reconnect
* degesch: make /query without arguments just open the buffer
* degesch: add a censor plugin
* degesch: die on configuration parse errors
* degesch: request channel modes also on rejoin
* degesch: don't show remembered channel modes on parted channels
* degesch: fix highlight detection in colored text
* degesch: fix CTCP handling for the real world and don't decode X-QUOTEs
* degesch: add support for OpenSSL 1.1.0
0.9.4 (2016-04-28) "Oops"
* degesch: fix crash on characters invalid in Windows-1252
* degesch: add an auto-rejoin plugin
* degesch: better date change messages with customizable formatting;
now also used in the backlog, so it looks closer to regular output
* ZyklonB: add a calc plugin providing a basic Scheme REPL
* ZyklonB: add a seen plugin
* kike, ZyklonB: use pledge(2) on OpenBSD
0.9.3 (2016-03-27) "Doesn't Even Suck"
* Use TLS Server Name Indication when connecting to servers
* degesch: now we erase the screen before displaying buffers
* degesch: implemented word wrapping in buffers
* degesch: added autocomplete for /topic
* degesch: Lua API was improved and extended
* degesch: added a basic last.fm "now playing" plugin
* degesch: backlog limit was made configurable
* degesch: allow changing the list of IRC capabilities to use if available
* degesch: optimize buffer memory usage
* degesch: added logging of messages sent from /quote and plugins
* degesch: M-! and M-a to go to the next buffer in order with
a highlight or new activity respectively
* degesch: added --format for previewing things like MOTD files
* degesch: added /buffer goto supporting case insensitive partial matches
* kike: add support for IRCv3.2 server-time
* ZyklonB: plugins now run in a dedicated data directory
* ZyklonB: added a factoids plugin
* Remote addresses are now resolved asynchronously
* Various bugfixes
0.9.2 (2015-12-31)
* degesch: added rudimentary support for Lua scripting

View File

@@ -2,15 +2,15 @@ uirc3
=====
:compact-option:
The unethical 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.
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.
All of them have these potentially interesting properties:
- full IPv6 support
- IPv6 support
- TLS support, including client certificates
- minimal dependencies
- lean on dependencies (with the exception of 'degesch')
- compact and arguably easy to hack on
- permissive license
@@ -20,11 +20,13 @@ The IRC client. It is largely defined by being built on top of GNU Readline
that has been hacked to death. Its interface should feel somewhat familiar for
weechat or irssi users.
image::degesch.png[align="center"]
This is the largest application within the project. It has most of the stuff
you'd expect of an IRC client, such as being able to set up multiple servers,
a powerful configuration system, integrated help, text formatting, CTCP queries,
automatic splitting of overlong messages, autocomplete, logging to file,
auto-away, command aliases and rudimentary support for Lua scripting.
auto-away, command aliases and basic support for Lua scripting.
kike
----
@@ -63,14 +65,19 @@ the rest of the package.
It survives crashes, server disconnects and timeouts, and also has native SOCKS
support (even though socksify can add that easily to any program).
Packages
--------
Regular releases are sporadic. git master should be stable enough. You can get
a package with the latest development version from Archlinux's AUR.
Building
--------
Build dependencies: CMake, pkg-config, help2man, awk, sh, liberty (included) +
Runtime dependencies: openssl, curses (degesch),
readline >= 6.0 or libedit >= 2013-07-12 (degesch),
lua >= 5.3 (degesch, optional)
Runtime dependencies: openssl +
Additionally for degesch: curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12
$ git clone --recursive https://github.com/pjanouch/uirc3.git
$ git clone --recursive https://git.janouch.name/p/uirc3.git
$ mkdir uirc3/build
$ cd uirc3/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
@@ -86,11 +93,8 @@ Or you can try telling CMake to make a package for you. For Debian it is:
$ cpack -G DEB
# dpkg -i uirc3-*.deb
Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
`fakeroot` or file ownership will end up wrong.
Running
-------
Usage
-----
'degesch' has in-program configuration. Just run it and read the instructions.
For the rest you might want to generate a configuration file:
@@ -118,23 +122,65 @@ To get the fingerprint from a certificate file in the required form, use:
$ openssl x509 -in public.pem -outform DER | sha1sum
Custom Key Bindings in degesch
------------------------------
The default and preferred frontend used in 'degesch' is GNU Readline. This
means that you can change your bindings by editing '~/.inputrc'. For example:
....
# Preload with system-wide settings
$include /etc/inputrc
# Make M-left and M-right reorder buffers
$if degesch
"\e\e[C": move-buffer-right
"\e\e[D": move-buffer-left
$endif
....
Consult the source code and the GNU Readline manual for a list of available
functions. Also refer to the latter for the exact syntax of this file.
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.
/set behaviour.date_change_line = "%a %e %b %Y"
/set behaviour.plugin_autoload += "fancy-prompt.lua,thin-cursor.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_strip_formatting = off
/set attributes.reset = "\x1b[0m"
/set attributes.userhost = "\x1b[38;5;109m"
/set attributes.join = "\x1b[38;5;108m"
/set attributes.part = "\x1b[38;5;138m"
/set attributes.external = "\x1b[38;5;248m"
/set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m"
Configuration profiles
----------------------
Even though the applications don't directly support configuration profiles,
they conform to the XDG standard, and thus you can change the location they
load configuration from via XDG_CONFIG_HOME (normally '~/.config') and the
location where store their data via XDG_DATA_HOME (normally '~/.local/share').
It would be relatively easy to make the applications assume whatever name you
run them under (for example by using symbolic links), and load different
configurations accordingly, but I consider it rather messy and unnecessary.
Contributing and Support
------------------------
Use this project's GitHub to report any bugs, request features, or submit pull
requests. If you want to discuss this project, or maybe just hang out with
the developer, feel free to join me at irc://anathema.irc.so, channel #anathema.
Use https://git.janouch.name/p/uirc3 to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
Disclaimer
----------
I am not an antisemitist, I'm just being an offensive asshole with the naming.
And no, I'm not going to change the names.
Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
License
-------
'uirc3' is written by Přemysl Janouch <p.janouch@gmail.com>.
This software is released under the terms of the 0BSD license, the text of which
is included within the package along with the list of authors.
You may use the software under the terms of the ISC license, the text of which
is included within the package, or, at your option, you may relicense the work
under the MIT or the Modified BSD License, as listed at the following site:
http://www.gnu.org/licenses/license-list.html
Note that 'degesch' technically becomes GPL-licensed when you statically link it
against GNU Readline, but that is not a concern of this source package.

123
common.c
View File

@@ -1,11 +1,10 @@
/*
* common.c: common functionality
*
* Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
* Copyright (c) 2014 - 2015, Přemysl Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -18,6 +17,7 @@
*/
#define LIBERTY_WANT_SSL
#define LIBERTY_WANT_ASYNC
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_PROTO_IRC
@@ -33,20 +33,32 @@
#include <arpa/inet.h>
#include <netinet/tcp.h>
/// Shorthand to set an error and return failure from the function
#define FAIL(...) \
BLOCK_START \
error_set (e, __VA_ARGS__); \
return 0; \
BLOCK_END
#define CONTAINER_OF(pointer, type, member) \
(type *) ((char *) pointer - offsetof (type, member))
static void
init_openssl (void)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
SSL_library_init ();
// XXX: this list is probably not complete
atexit (EVP_cleanup);
SSL_load_error_strings ();
atexit (ERR_free_strings);
#else
// Cleanup is done automatically via atexit()
OPENSSL_init_ssl (0, NULL);
#endif
}
// --- To be moved to liberty --------------------------------------------------
static void
cstr_set (char **s, char *new)
{
free (*s);
*s = new;
}
static ssize_t
str_vector_find (const struct str_vector *v, const char *s)
strv_find (const struct strv *v, const char *s)
{
for (size_t i = 0; i < v->len; i++)
if (!strcmp (v->vector[i], s))
@@ -54,18 +66,35 @@ str_vector_find (const struct str_vector *v, const char *s)
return -1;
}
static time_t
unixtime_msec (long *msec)
{
#ifdef _POSIX_TIMERS
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
*msec = tp.tv_nsec / 1000000;
#else // ! _POSIX_TIMERS
struct timeval tp;
hard_assert (gettimeofday (&tp, NULL) != -1);
*msec = tp.tv_usec / 1000;
#endif // ! _POSIX_TIMERS
return tp.tv_sec;
}
/// This differs from the non-unique version in that we expect the filename
/// to be something like a pattern for mkstemp(), so the resulting path can
/// reside in a system-wide directory with no risk of a conflict.
static char *
resolve_relative_runtime_unique_filename (const char *filename)
{
struct str path;
str_init (&path);
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
const char *tmpdir = getenv ("TMPDIR");
struct str path = str_make ();
if (runtime_dir && *runtime_dir == '/')
str_append (&path, runtime_dir);
else if (tmpdir && *tmpdir == '/')
str_append (&path, tmpdir);
else
str_append (&path, "/tmp");
str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
@@ -92,7 +121,7 @@ xwrite (int fd, const char *data, size_t len, struct error **e)
if (res >= 0)
written += res;
else if (errno != EINTR)
FAIL ("%s", strerror (errno));
return error_set (e, "%s", strerror (errno));
}
return true;
}
@@ -223,7 +252,7 @@ struct socks_connector
// You may destroy the connector object in these two main callbacks:
/// Connection has been successfully established
void (*on_connected) (void *user_data, int socket);
void (*on_connected) (void *user_data, int socket, const char *hostname);
/// Failed to establish a connection to either target
void (*on_failure) (void *user_data);
@@ -308,7 +337,6 @@ socks_4a_start (struct socks_connector *self)
}
struct str *wb = &self->write_buffer;
str_init (wb);
str_pack_u8 (wb, 4); // version
str_pack_u8 (wb, 1); // connect
@@ -594,9 +622,11 @@ socks_connector_on_timeout (struct socks_connector *self)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
socks_connector_on_connected (void *user_data, int socket_fd)
socks_connector_on_connected
(void *user_data, int socket_fd, const char *hostname)
{
set_blocking (socket_fd, false);
(void) hostname;
struct socks_connector *self = user_data;
self->socket_fd = socket_fd;
@@ -658,20 +688,8 @@ socks_connector_start (struct socks_connector *self)
connector->on_error = socks_connector_on_error;
connector->on_failure = socks_connector_on_failure;
struct error *e = NULL;
if (!connector_add_target (connector, self->hostname, self->service, &e))
{
if (self->on_error)
self->on_error (self->user_data, e->message);
error_free (e);
socks_connector_destroy_connector (self);
socks_connector_fail (self);
return;
}
connector_add_target (connector, self->hostname, self->service);
poller_timer_set (&self->timeout, 60 * 1000);
connector_step (connector);
self->done = false;
self->bound_port = 0;
@@ -689,7 +707,7 @@ socks_try_fill_read_buffer (struct socks_connector *self, size_t n)
return true;
ssize_t received;
str_ensure_space (&self->read_buffer, remains);
str_reserve (&self->read_buffer, remains);
do
received = recv (self->socket_fd,
self->read_buffer.str + self->read_buffer.len, remains, 0);
@@ -713,8 +731,8 @@ socks_call_on_data (struct socks_connector *self)
if (self->read_buffer.len < to_consume)
return true;
struct msg_unpacker unpacker;
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
struct msg_unpacker unpacker =
msg_unpacker_make (self->read_buffer.str, self->read_buffer.len);
bool result = self->on_data (self, &unpacker);
str_remove_slice (&self->read_buffer, 0, to_consume);
return result;
@@ -762,8 +780,10 @@ socks_connector_on_ready
int fd = self->socket_fd;
self->socket_fd = -1;
struct socks_target *target = self->targets_iter;
set_blocking (fd, true);
self->on_connected (self->user_data, fd);
self->on_connected (self->user_data, fd, target->address_str);
}
else
// We've failed this target, let's try to move on
@@ -777,16 +797,16 @@ socks_connector_init (struct socks_connector *self, struct poller *poller)
{
memset (self, 0, sizeof *self);
poller_fd_init (&self->socket_event, poller, (self->socket_fd = -1));
self->socket_event = poller_fd_make (poller, (self->socket_fd = -1));
self->socket_event.dispatcher = (poller_fd_fn) socks_connector_on_ready;
self->socket_event.user_data = self;
poller_timer_init (&self->timeout, poller);
self->timeout = poller_timer_make (poller);
self->timeout.dispatcher = (poller_timer_fn) socks_connector_on_timeout;
self->timeout.user_data = self;
str_init (&self->read_buffer);
str_init (&self->write_buffer);
self->read_buffer = str_make ();
self->write_buffer = str_make ();
}
static void
@@ -887,8 +907,8 @@ static struct ctcp_chunk *
ctcp_chunk_new (void)
{
struct ctcp_chunk *self = xcalloc (1, sizeof *self);
str_init (&self->tag);
str_init (&self->text);
self->tag = str_make ();
self->text = str_make ();
return self;
}
@@ -948,6 +968,13 @@ ctcp_intra_decode (const char *chunk, size_t len, struct str *output)
}
}
// According to the original CTCP specification we should use
// ctcp_intra_decode() on all parts, however no one seems to use that
// and it breaks normal text with backslashes
#ifndef SUPPORT_CTCP_X_QUOTES
#define ctcp_intra_decode(s, len, output) str_append_data (output, s, len)
#endif
static void
ctcp_parse_tagged (const char *chunk, size_t len, struct ctcp_chunk *output)
{
@@ -970,15 +997,11 @@ ctcp_parse_tagged (const char *chunk, size_t len, struct ctcp_chunk *output)
static struct ctcp_chunk *
ctcp_parse (const char *message)
{
struct str m;
str_init (&m);
struct str m = str_make ();
ctcp_low_level_decode (message, &m);
struct ctcp_chunk *result = NULL, *result_tail = NULL;
// According to the original CTCP specification we should use
// ctcp_intra_decode() on all parts, however no one seems to
// use that and it breaks normal text with backslashes
size_t start = 0;
bool in_ctcp = false;
for (size_t i = 0; i < m.len; i++)
@@ -1002,7 +1025,7 @@ ctcp_parse (const char *message)
if (my_is_ctcp)
ctcp_parse_tagged (m.str + my_start, i - my_start, chunk);
else
str_append_data (&chunk->text, m.str + my_start, i - my_start);
ctcp_intra_decode (m.str + my_start, i - my_start, &chunk->text);
LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
}
@@ -1016,7 +1039,7 @@ ctcp_parse (const char *message)
chunk->is_partial = true;
}
else
str_append_data (&chunk->text, m.str + start, m.len - start);
ctcp_intra_decode (m.str + start, m.len - start, &chunk->text);
LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
}

View File

@@ -1,11 +1,13 @@
#ifndef CONFIG_H
#define CONFIG_H
#define PROGRAM_VERSION "${project_VERSION}"
#define PROGRAM_VERSION "${project_version}"
#define ZYKLONB_PLUGIN_DIR "${CMAKE_INSTALL_PREFIX}/${zyklonb_plugin_dir}"
#cmakedefine HAVE_READLINE
#cmakedefine HAVE_EDITLINE
#cmakedefine HAVE_LUA
#cmakedefine01 ICONV_ACCEPTS_TRANSLIT
#endif // ! CONFIG_H

6534
degesch.c

File diff suppressed because it is too large Load Diff

BIN
degesch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

731
kike.c

File diff suppressed because it is too large Load Diff

Submodule liberty updated: f6d74544f8...bb30c7d86e

View File

@@ -0,0 +1,48 @@
--
-- auto-rejoin.lua: join back automatically when someone kicks you
--
-- Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local timeout
degesch.setup_config {
timeout = {
type = "integer",
comment = "auto rejoin timeout",
default = "0",
on_change = function (v)
timeout = v
end,
validate = function (v)
if v < 0 then error ("timeout must not be negative", 0) end
end,
},
}
async, await = degesch.async, coroutine.yield
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
if msg.command ~= "KICK" then return line end
local who = msg.prefix:match ("^[^!]*")
local channel, whom = table.unpack (msg.params)
if who ~= whom and whom == server.user.nickname then
async.go (function ()
await (async.timer_ms (timeout * 1000))
server:send ("JOIN " .. channel)
end)
end
return line
end)

View File

@@ -0,0 +1,73 @@
--
-- censor.lua: black out certain users' messages
--
-- Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local to_pattern = function (mask)
if not mask:match ("!") then mask = mask .. "!*" end
if not mask:match ("@") then mask = mask .. "@*" end
-- That is, * acts like a wildcard, otherwise everything is escaped
return "^" .. mask:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
:gsub ("%*", ".*") .. "$"
end
local patterns = {}
local read_masks = function (v)
patterns = {}
local add = function (who, where)
local channels = patterns[who] or {}
table.insert (channels, where)
patterns[who] = channels
end
for item in v:lower ():gmatch ("[^,]+") do
local who, where = item:match ("^([^/]+)/*(.*)")
if who then add (to_pattern (who), where == "" or where) end
end
end
degesch.setup_config {
masks = {
type = "string_array",
default = "\"\"",
comment = "user masks (optionally \"/#channel\") to censor",
on_change = read_masks
},
}
local censor = function (line)
-- Taking a shortcut to avoid lengthy message reassembly
local start, text = line:match ("^(.- PRIVMSG .-:)(.*)$")
local ctcp, rest = text:match ("^(\x01%g+ )(.*)")
text = ctcp and ctcp .. "\x0301,01" .. rest or "\x0301,01" .. text
return start .. text
end
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
if msg.command ~= "PRIVMSG" then return line end
local channel = msg.params[1]:lower ()
for who, where in pairs (patterns) do
if msg.prefix:lower ():match (who) then
for _, x in pairs (where) do
if x == true or x == channel then
return censor (line)
end
end
end
end
return line
end)

View File

@@ -0,0 +1,104 @@
--
-- fancy-prompt.lua: the fancy multiline prompt you probably want
--
-- Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
-- Beware that it is a hack and only goes about 90% of the way, which is why
-- this functionality is only available as a plugin in the first place
-- (well, and also for customizability).
--
-- The biggest problem is that the way we work with Readline is incompatible
-- with multiline prompts, and normal newlines just don't work. This is being
-- circumvented by using an overflowing single-line prompt with a specially
-- crafted character in the rightmost column that prevents the bar's background
-- from spilling all over the last line.
--
-- There is also a problem with C-r search rendering not clearing out the
-- background but to really fix that mode, we'd have to fully reimplement it
-- since its alternative prompt very often gets overriden by accident anyway.
degesch.hook_prompt (function (hook)
local current = degesch.current_buffer
local chan = current.channel
local s = current.server
local bg_color = "255"
local current_n = 0
local active = ""
for i, buffer in ipairs (degesch.buffers) do
if buffer == current then
current_n = i
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
if active ~= "" then active = active .. "," end
if buffer.highlighted then
active = active .. "!"
bg_color = "224"
end
active = active .. i
end
end
if active ~= "" then active = "(" .. active .. ")" end
local x = current_n .. ":" .. current.name
if chan and chan.users_len ~= 0 then
local params = ""
for mode, param in pairs (chan.param_modes) do
params = params .. " +" .. mode .. " " .. param
end
local modes = chan.no_param_modes .. params:sub (3)
if modes ~= "" then x = x .. "(+" .. modes .. ")" end
x = x .. "{" .. chan.users_len .. "}"
end
if current.hide_unimportant then x = x .. "<H>" end
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]", "?")
-- Cut off extra characters and apply formatting, including the hack.
-- Note that this doesn't count with full-width or zero-width characters.
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" ..
x .. "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02"
local user_prefix = function (chan, user)
for i, chan_user in ipairs (chan.users) do
if chan_user.user == user then return chan_user.prefixes end
end
return ""
end
if s then
x = x .. "["
local state = s.state
if state == "disconnected" or state == "connecting" then
x = x .. "(" .. state .. ")"
elseif state ~= "registered" then
x = x .. "(unregistered)"
else
local user, modes = s.user, s.user_mode
if chan then x = x .. user_prefix (chan, user) end
x = x .. user.nickname
if modes ~= "" then x = x .. "(" .. modes .. ")" end
end
x = x .. "] "
else
-- There needs to be at least one character so that the cursor
-- doesn't get damaged by our hack in that last column
x = x .. "> "
end
return x
end)

178
plugins/degesch/last-fm.lua Normal file
View File

@@ -0,0 +1,178 @@
--
-- last-fm.lua: "now playing" feature using the last.fm API
--
-- Dependencies: lua-cjson (from luarocks e.g.)
--
-- I call this style closure-oriented programming
--
-- Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local cjson = require "cjson"
-- Setup configuration to load last.fm API credentials from
local user, api_key
degesch.setup_config {
user = {
type = "string",
comment = "last.fm username",
on_change = function (v) user = v end
},
api_key = {
type = "string",
comment = "last.fm API key",
on_change = function (v) api_key = v end
},
}
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- Generic error reporting
local report_error = function (buffer, error)
buffer:log ("last-fm error: " .. error)
end
-- Process data return by the server and extract the now playing song
local process = function (buffer, data, action)
-- There's no reasonable Lua package to parse HTTP that I could find
local s, e, v, status, message = string.find (data, "(%S+) (%S+) .+\r\n")
if not s then return "server returned unexpected data" end
if status ~= "200" then return status .. " " .. message end
local s, e = string.find (data, "\r\n\r\n")
if not s then return "server returned unexpected data" end
local parser = cjson.new ()
data = parser.decode (string.sub (data, e + 1))
if not data.recenttracks or not data.recenttracks.track then
return "invalid response" end
-- Need to make some sense of the XML automatically converted to JSON
local text_of = function (node)
if type (node) ~= "table" then return node end
return node["#text"] ~= "" and node["#text"] or nil
end
local name, artist, album
for i, track in ipairs (data.recenttracks.track) do
if track["@attr"] and track["@attr"].nowplaying then
if track.name then name = text_of (track.name) end
if track.artist then artist = text_of (track.artist) end
if track.album then album = text_of (track.album) end
end
end
if not name then
action (false)
else
local np = "\"" .. name .. "\""
if artist then np = np .. " by " .. artist end
if album then np = np .. " from " .. album end
action (np)
end
end
-- Set up the connection and make the request
local on_connected = function (buffer, c, host, action)
-- Buffer data in the connection object
c.data = ""
c.on_data = function (data)
c.data = c.data .. data
end
-- And process it after we receive everything
c.on_eof = function ()
error = process (buffer, c.data, action)
if error then report_error (buffer, error) end
c:close ()
end
c.on_error = function (e)
report_error (buffer, e)
end
-- Make the unencrypted HTTP request
local url = "/2.0/?method=user.getrecenttracks&user=" .. user ..
"&limit=1&api_key=" .. api_key .. "&format=json"
c:send ("GET " .. url .. " HTTP/1.1\r\n")
c:send ("User-agent: last-fm.lua\r\n")
c:send ("Host: " .. host .. "\r\n")
c:send ("Connection: close\r\n")
c:send ("\r\n")
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- Avoid establishing more than one connection at a time
local running
-- Initiate a connection to last.fm servers
async, await = degesch.async, coroutine.yield
local make_request = function (buffer, action)
if not user or not api_key then
report_error (buffer, "configuration is incomplete")
return
end
if running then running:cancel () end
running = async.go (function ()
local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80))
if e then
report_error (buffer, e)
else
on_connected (buffer, c, host, action)
end
running = nil
end)
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local now_playing
local tell_song = function (buffer)
if now_playing == nil then
buffer:log ("last-fm: I don't know what you're listening to")
elseif not now_playing then
buffer:log ("last-fm: not playing anything right now")
else
buffer:log ("last-fm: now playing: " .. now_playing)
end
end
local send_song = function (buffer)
if not now_playing then
tell_song (buffer)
else
buffer:execute ("/me is listening to " .. now_playing)
end
end
-- Hook input to simulate new commands
degesch.hook_input (function (hook, buffer, input)
if input == "/np" then
make_request (buffer, function (np)
now_playing = np
send_song (buffer)
end)
elseif input == "/np?" then
make_request (buffer, function (np)
now_playing = np
tell_song (buffer)
end)
elseif input == "/np!" then
send_song (buffer)
else
return input
end
end)

View File

@@ -1,11 +1,10 @@
--
-- ping-timeout.lua: ping timeout readability enhancement plugin
--
-- Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
-- Copyright (c) 2015 - 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -17,9 +16,9 @@
--
degesch.hook_irc (function (hook, server, line)
local start, timeout =
line:match ("^(:[^ ]* QUIT :Ping timeout:) (%d+) seconds$")
if not start then
local msg = degesch.parse (line)
local start, timeout = line:match ("^(.* :Ping timeout:) (%d+) seconds$")
if msg.command ~= "QUIT" or not start then
return line
end

147
plugins/degesch/slack.lua Normal file
View File

@@ -0,0 +1,147 @@
--
-- slack.lua: try to fix up UX when using the Slack IRC gateway
--
-- Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local servers = {}
local read_servers = function (v)
servers = {}
for name in v:lower ():gmatch "[^,]+" do
servers[name] = true
end
end
-- This is a reverse list of Slack's automatic emoji, noseless forms
local unemojify, emoji, emoji_default = false, {}, {
heart = "<3",
broken_heart = "</3",
sunglasses = "8)",
anguished = "D:",
cry = ":'(",
monkey_face = ":o)",
kiss = ":*",
smiley = "=)",
smile = ":D",
wink = ";)",
laughing = ":>",
neutral_face = ":|",
open_mouth = ":o",
angry = ">:(",
slightly_smiling_face = ":)",
disappointed = ":(",
confused = ":/",
stuck_out_tongue = ":p",
stuck_out_tongue_winking_eye = ";p",
}
local load_emoji = function (extra)
emoji = {}
for k, v in pairs (emoji_default) do emoji[k] = v end
for k, v in extra:gmatch "([^,]+) ([^,]+)" do emoji[k] = v end
end
degesch.setup_config {
servers = {
type = "string_array",
default = "\"\"",
comment = "list of server names that are Slack IRC gateways",
on_change = read_servers
},
unemojify = {
type = "boolean",
default = "true",
comment = "convert emoji to normal ASCII emoticons",
on_change = function (v) unemojify = v end
},
extra_emoji = {
type = "string_array",
default = "\"grinning :)),joy :'),innocent o:),persevere >_<\"",
comment = "overrides or extra emoji for unemojify",
on_change = function (v) load_emoji (v) end
}
}
-- We can handle external messages about what we've supposedly sent just fine,
-- so let's get rid of that "[username] some message sent from the web UI" crap
degesch.hook_irc (function (hook, server, line)
local msg, us = degesch.parse (line), server.user
if not servers[server.name] or msg.command ~= "PRIVMSG" or not us
or msg.params[1]:lower () ~= us.nickname:lower () then return line end
-- Taking a shortcut to avoid lengthy message reassembly
local quoted_nick = us.nickname:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
local text = line:match ("^.- PRIVMSG .- :%[" .. quoted_nick .. "%] (.*)$")
if not text then return line end
return ":" .. us.nickname .. "!" .. server.irc_user_host .. " PRIVMSG "
.. msg.prefix:match "^[^!@]*" .. " :" .. text
end)
-- Unfuck emoji and :nick!nick@irc.tinyspeck.com MODE #channel +v nick : active
degesch.hook_irc (function (hook, server, line)
if not servers[server.name] then return line end
if unemojify then
local start, text = line:match ("^(.- PRIVMSG .-:)(.*)$")
if start then return start .. text:gsub (":([a-z_]+):", function (name)
if emoji[name] then return emoji[name] end
return ":" .. name .. ":"
end) end
end
return line:gsub ("^(:%S+ MODE .+) : .*", "%1")
end)
-- The gateway simply ignores the NAMES command altogether
degesch.hook_input (function (hook, buffer, input)
if not buffer.channel or not servers[buffer.server.name]
or not input:match "^/names%s*$" then return input end
local users = buffer.channel.users
table.sort (users, function (a, b)
if a.prefixes > b.prefixes then return true end
if a.prefixes < b.prefixes then return false end
return a.user.nickname < b.user.nickname
end)
local names = "Users on " .. buffer.channel.name .. ":"
for i, chan_user in ipairs (users) do
names = names .. " " .. chan_user.prefixes .. chan_user.user.nickname
end
buffer:log (names)
end)
degesch.hook_completion (function (hook, data, word)
local chan = degesch.current_buffer.channel
local server = degesch.current_buffer.server
if not chan or not servers[server.name] then return end
-- In /commands there is typically no desire at all to add the at sign
if data.location == 1 and data.words[1]:match "^/" then return end
-- Handle both when the at sign is already there and when it is not
local needle = word:gsub ("^@", ""):lower ()
local t = {}
local try = function (name)
if data.location == 0 then name = name .. ":" end
if name:sub (1, #needle):lower () == needle then
table.insert (t, "@" .. name)
end
end
for _, chan_user in ipairs (chan.users) do
try (chan_user.user.nickname)
end
for _, special in ipairs { "channel", "here" } do
try (special)
end
return t
end)

View File

@@ -0,0 +1,27 @@
--
-- thin-cursor.lua: set a thin cursor
--
-- Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
-- If tmux doesn't work, add the following to its configuration:
-- set -as terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q'
-- Change the "2" as per http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
local out = io.output ()
out:write ("\x1b[6 q"):flush ()
-- By registering a global variable, we get notified about plugin unload
x = setmetatable ({}, { __gc = function ()
out:write ("\x1b[2 q"):flush ()
end })

View File

@@ -1,11 +1,10 @@
--
-- utm-filter.lua: filter out Google Analytics bullshit from URLs
--
-- Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
-- Copyright (c) 2015, Přemysl Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

241
plugins/zyklonb/calc Executable file
View File

@@ -0,0 +1,241 @@
#!/usr/bin/env guile
ZyklonB calc plugin, basic Scheme evaluator
Copyright 2016 Přemysl Janouch
See the file LICENSE for licensing information.
!#
(import (rnrs (6)))
(use-modules ((rnrs) :version (6)))
; --- Message parsing ----------------------------------------------------------
(define-record-type message (fields prefix command params))
(define (parse-message line)
(let f ([parts '()] [chars (string->list line)])
(define (take-word w chars)
(if (or (null? chars) (eqv? (car chars) #\x20))
(f (cons (list->string (reverse w)) parts)
(if (null? chars) chars (cdr chars)))
(take-word (cons (car chars) w) (cdr chars))))
(if (null? chars)
(let ([data (reverse parts)])
(when (< (length data) 2)
(error 'parse-message "invalid message"))
(make-message (car data) (cadr data) (cddr data)))
(if (null? parts)
(if (eqv? (car chars) #\:)
(take-word '() (cdr chars))
(f (cons #f parts) chars))
(if (eqv? (car chars) #\:)
(f (cons (list->string (cdr chars)) parts) '())
(take-word '() chars))))))
; --- Utilities ----------------------------------------------------------------
(define (display-exception e port)
(define (puts . x)
(for-all (lambda (a) (display a port)) x)
(newline port))
(define (record-fields rec)
(let* ([rtd (record-rtd rec)]
[v (record-type-field-names rtd)]
[len (vector-length v)])
(map (lambda (k i) (cons k ((record-accessor rtd i) rec)))
(vector->list v)
(let c ([i len] [ls '()])
(if (= i 0) ls (c (- i 1) (cons (- i 1) ls)))))))
(puts "Caught " (record-type-name (record-rtd e)))
(for-all
(lambda (subtype)
(puts " " (record-type-name (record-rtd subtype)))
(for-all
(lambda (field) (puts " " (car field) ": " (cdr field)))
(record-fields subtype)))
(simple-conditions e)))
; XXX - we have to work around Guile's lack of proper eol-style support
(define xc (make-transcoder (latin-1-codec) 'lf 'replace))
(define irc-input-port (transcoded-port (standard-input-port) xc))
(define irc-output-port (transcoded-port (standard-output-port) xc))
(define (send . message)
(for-all (lambda (x) (display x irc-output-port)) message)
(display #\return irc-output-port)
(newline irc-output-port)
(flush-output-port irc-output-port))
(define (get-line-crlf port)
(define line (get-line port))
(if (eof-object? line) line
(let ([len (string-length line)])
(if (and (> len 0) (eqv? (string-ref line (- len 1)) #\return))
(substring line 0 (- len 1)) line))))
(define (get-config name)
(send "ZYKLONB get_config :" name)
(car (message-params (parse-message (get-line-crlf irc-input-port)))))
(define (extract-nick prefix)
(do ([i 0 (+ i 1)] [len (string-length prefix)])
([or (= i len) (char=? #\! (string-ref prefix i))]
[substring prefix 0 i])))
(define (string-after s start)
(let ([s-len (string-length s)] [with-len (string-length start)])
(and (>= s-len with-len)
(string=? (substring s 0 with-len) start)
(substring s with-len s-len))))
; --- Calculator ---------------------------------------------------------------
; Evaluator derived from the example in The Scheme Programming Language.
;
; Even though EVAL with a carefully crafted environment would also do a good
; job at sandboxing, it would probably be impossible to limit execution time...
(define (env-new formals actuals env)
(cond [(null? formals) env]
[(symbol? formals) (cons (cons formals actuals) env)]
[else (cons (cons (car formals) (car actuals))
(env-new (cdr formals) (cdr actuals) env))]))
(define (env-lookup var env) (cdr (assq var env)))
(define (env-assign var val env) (set-cdr! (assq var env) val))
(define (check-reductions r)
(if (= (car r) 0)
(error 'check-reductions "reduction limit exceeded")
(set-car! r (- (car r) 1))))
; TODO - think about implementing more syntactical constructs,
; however there's not much point in having anything else in a calculator...
(define (exec expr r env)
(check-reductions r)
(cond [(symbol? expr) (env-lookup expr env)]
[(pair? expr)
(case (car expr)
[(quote) (cadr expr)]
[(lambda) (lambda vals
(let ([env (env-new (cadr expr) vals env)])
(let loop ([exprs (cddr expr)])
(if (null? (cdr exprs))
(exec (car exprs) r env)
(begin (exec (car exprs) r env)
(loop (cdr exprs)))))))]
[(if) (if (exec (cadr expr) r env)
(exec (caddr expr) r env)
(exec (cadddr expr) r env))]
[(set!) (env-assign (cadr expr) (exec (caddr expr) r env) env)]
[else (apply (exec (car expr) r env)
(map (lambda (x) (exec x r env)) (cdr expr)))])]
[else expr]))
(define-syntax forward
(syntax-rules ()
[(_) '()]
[(_ a b ...) (cons (cons (quote a) a) (forward b ...))]))
; ...which can't prevent me from simply importing most of the standard library
(define base-library
(forward
; Equivalence, procedure predicate, booleans
eqv? eq? equal? procedure? boolean? boolean=? not
; numbers, numerical input and output
number? complex? real? rational? integer? exact? inexact? exact inexact
real-valued? rational-valued? integer-valued? number->string string->number
; Arithmetic
= < > <= >= zero? positive? negative? odd? even? finite? infinite? nan?
min max + * - / abs div-and-mod div mod div0-and-mod0 div0 mod0
gcd lcm numerator denominator floor ceiling truncate round
rationalize exp log sin cos tan asin acos atan sqrt expt
make-rectangular make-polar real-part imag-part magnitude angle
; Pairs and lists
map for-each cons car cdr caar cadr cdar cddr
caaar caadr cadar caddr cdaar cdadr cddar cdddr
caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
pair? null? list? list length append reverse list-tail list-ref
; Symbols
symbol? symbol=? symbol->string string->symbol
; Characters
char? char=? char<? char>? char<=? char>=? char->integer integer->char
; Strings; XXX - omitted make-string - can cause OOM
string? string=? string<? string>? string<=? string>=?
string string-length string-ref substring
string-append string->list list->string string-for-each string-copy
; Vectors; XXX - omitted make-vector - can cause OOM
vector? vector vector-length vector-ref vector-set!
vector->list list->vector vector-fill! vector-map vector-for-each
; Control features
apply call/cc values call-with-values dynamic-wind))
(define extended-library
(forward
char-upcase char-downcase char-titlecase char-foldcase
char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
char-alphabetic? char-numeric? char-whitespace?
char-upper-case? char-lower-case? char-title-case?
string-upcase string-downcase string-titlecase string-foldcase
string-ci=? string-ci<? string-ci>? string-ci<=? string-ci>=?
find for-all exists filter partition fold-left fold-right
remp remove remv remq memp member memv memq assp assoc assv assq cons*
list-sort vector-sort vector-sort!
bitwise-not bitwise-and bitwise-ior bitwise-xor bitwise-if
bitwise-bit-count bitwise-length bitwise-first-bit-set bitwise-bit-set?
bitwise-copy-bit bitwise-bit-field bitwise-copy-bit-field
bitwise-arithmetic-shift bitwise-rotate-bit-field bitwise-reverse-bit-field
bitwise-arithmetic-shift-left bitwise-arithmetic-shift-right
set-car! set-cdr! string-set! string-fill!))
(define (interpret expr)
(exec expr '(2000) (append base-library extended-library)))
; We could show something a bit nicer but it would be quite Guile-specific
(define (error-string e)
(map (lambda (x) (string-append " " (symbol->string x)))
(filter (lambda (x) (not (member x '(&who &message &irritants &guile))))
(map (lambda (x) (record-type-name (record-rtd x)))
(simple-conditions e)))))
(define (calc input respond)
(define (stringify x)
(call-with-string-output-port (lambda (port) (write x port))))
(guard (e [else (display-exception e (current-error-port))
(apply respond "caught" (error-string e))])
(let* ([input (open-string-input-port input)]
[data (let loop ()
(define datum (get-datum input))
(if (eof-object? datum) '() (cons datum (loop))))])
(call-with-values
(lambda () (interpret (list (append '(lambda ()) data))))
(lambda message
(for-all (lambda (x) (respond (stringify x))) message))))))
; --- Main loop ----------------------------------------------------------------
(define prefix (get-config "prefix"))
(send "ZYKLONB register")
(define (process msg)
(when (string-ci=? (message-command msg) "PRIVMSG")
(let* ([nick (extract-nick (message-prefix msg))]
[target (car (message-params msg))]
[response-begin
(apply string-append "PRIVMSG "
(if (memv (string-ref target 0) (string->list "#&!+"))
`(,target " :" ,nick ": ") `(,nick " :")))]
[respond (lambda args (apply send response-begin args))]
[text (cadr (message-params msg))]
[input (or (string-after text (string-append prefix "calc "))
(string-after text (string-append prefix "= ")))])
(when input (calc input respond)))))
(let main-loop ()
(define line (get-line-crlf irc-input-port))
(unless (eof-object? line)
(guard (e [else (display-exception e (current-error-port))])
(unless (string=? "" line)
(process (parse-message line))))
(main-loop)))

177
plugins/zyklonb/factoids Executable file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env perl
#
# ZyklonB factoids plugin
#
# Copyright 2016 Přemysl Janouch <p@janouch.name>
# See the file LICENSE for licensing information.
#
use strict;
use warnings;
use Text::Wrap;
# --- IRC protocol -------------------------------------------------------------
binmode STDIN; select STDIN; $| = 1; $/ = "\r\n";
binmode STDOUT; select STDOUT; $| = 1; $\ = "\r\n";
sub parse ($) {
chomp (my $line = shift);
return undef unless my ($nick, $user, $host, $command, $args) = ($line =~
qr/^(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(?: +(.*))?$/o);
return {nick => $nick, user => $user, host => $host, command => $command,
args => defined $args ? [$args =~ /:?((?<=:).*|[^ ]+) */og] : []};
}
sub bot_print {
print "ZYKLONB print :${\shift}";
}
# --- Initialization -----------------------------------------------------------
my %config;
for my $name (qw(prefix)) {
print "ZYKLONB get_config :$name";
$config{$name} = (parse <STDIN>)->{args}->[0];
}
print "ZYKLONB register";
# --- Database -----------------------------------------------------------------
# Simple map of (factoid_name => [definitions]); all factoids are separated
# by newlines and definitions by carriage returns. Both disallowed in IRC.
sub db_load {
local $/ = "\n";
my ($path) = @_;
open my $db, "<", $path or return {};
my %entries;
while (<$db>) {
chomp;
my @defs = split "\r";
$entries{shift @defs} = \@defs;
}
\%entries
}
sub db_save {
local $\ = "\n";
my ($path, $ref) = @_;
my $path_new = "$path.new";
open my $db, ">", $path_new or die "db save failed: $!";
my %entries = %$ref;
print $db join "\r", ($_, @{$entries{$_}}) for keys %entries;
close $db;
rename $path_new, $path or die "db save failed: $!";
}
# --- Factoids -----------------------------------------------------------------
my $db_path = 'factoids.db';
my %db = %{db_load $db_path};
sub learn {
my ($respond, $input) = @_;
return &$respond("usage: <name> = <definition>")
unless $input =~ /^([^=]+?)(?:\s+(\d+))?\s*=\s*(.+?)\s*$/;
my ($name, $number, $definition) = ($1, $2, $3);
return &$respond("trailing numbers in names are disallowed")
if defined $2;
$db{$name} = [] unless exists $db{$name};
my $entries = $db{$name};
return &$respond("duplicate definition")
if grep { lc $_ eq lc $definition } @$entries;
push @$entries, $definition;
&$respond("saved as #${\scalar @$entries}");
db_save $db_path, \%db;
}
sub check_number {
my ($respond, $name, $number) = @_;
my $entries = $db{$name};
if ($number > @$entries) {
&$respond(qq/"$name" has only ${\scalar @$entries} definitions/);
} elsif (not $number) {
&$respond("number must not be zero");
} else {
return 1;
}
return 0;
}
sub forget {
my ($respond, $input) = @_;
return &$respond("usage: <name> <number>")
unless $input =~ /^([^=]+?)\s+(\d+)\s*$/;
my ($name, $number) = ($1, int($2));
return &$respond(qq/"$name" is undefined/)
unless exists $db{$name};
my $entries = $db{$name};
return unless check_number $respond, $name, $number;
splice @$entries, --$number, 1;
&$respond("forgotten");
db_save $db_path, \%db;
}
sub whatis {
my ($respond, $input) = @_;
return &$respond("usage: <name> [<number>]")
unless $input =~ /^([^=]+?)(?:\s+(\d+))?\s*$/;
my ($name, $number) = ($1, $2);
return &$respond(qq/"$name" is undefined/)
unless exists $db{$name};
my $entries = $db{$name};
if (defined $number) {
return unless check_number $respond, $name, $number;
&$respond(qq/"$name" is #$number $entries->[$number - 1]/);
} else {
my $i = 1;
my $definition = join ", ", map { "#${\$i++} $_" } @{$entries};
&$respond(qq/"$name" is $definition/);
}
}
sub wildcard {
my ($respond, $input) = @_;
$input =~ /=/ ? learn(@_) : whatis(@_);
}
my %commands = (
'learn' => \&learn,
'forget' => \&forget,
'whatis' => \&whatis,
'??' => \&wildcard,
);
# --- Input loop ---------------------------------------------------------------
while (my $line = <STDIN>) {
my %msg = %{parse $line};
my @args = @{$msg{args}};
# This plugin only bothers to respond to PRIVMSG messages
next unless $msg{command} eq 'PRIVMSG' and @args >= 2
and my ($cmd, $input) = $args[1] =~ /^$config{prefix}(\S+)\s*(.*)/;
# So far the only reaction is a PRIVMSG back to the sender, so all the
# handlers need is a response callback and all arguments to the command
my ($target => $quote) = ($args[0] =~ /^[#+&!]/)
? ($args[0] => "$msg{nick}: ") : ($msg{nick} => '');
# Wrap all responses so that there's space for our prefix in the message
my $respond = sub {
local ($Text::Wrap::columns, $Text::Wrap::unexpand) = 400, 0;
my $start = "PRIVMSG $target :$quote";
print for split "\n", wrap $start, $start, shift;
};
&{$commands{$cmd}}($respond, $input) if exists($commands{$cmd});
}

160
plugins/zyklonb/seen Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env lua
--
-- ZyklonB seen plugin
--
-- Copyright 2016 Přemysl Janouch <p@janouch.name>
-- See the file LICENSE for licensing information.
--
function parse (line)
local msg = { params = {} }
line = line:match ("[^\r]*")
for start, word in line:gmatch ("()([^ ]+)") do
local colon = word:match ("^:(.*)")
if start == 1 and colon then
msg.prefix = colon
elseif not msg.command then
msg.command = word
elseif colon then
table.insert (msg.params, line:sub (start + 1))
break
elseif start ~= #line then
table.insert (msg.params, word)
end
end
return msg
end
function get_config (name)
io.write ("ZYKLONB get_config :", name, "\r\n")
return parse (io.read ()).params[1]
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
io.output ():setvbuf ('line')
local prefix = get_config ('prefix')
io.write ("ZYKLONB register\r\n")
local db = {}
local db_filename = "seen.db"
local db_garbage = 0
function remember (who, where, when, what)
if not db[who] then db[who] = {} end
if db[who][where] then db_garbage = db_garbage + 1 end
db[who][where] = { tonumber (when), what }
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local db_file, e = io.open (db_filename, "a+")
if not db_file then error ("cannot open database: " .. e, 0) end
function db_store (who, where, when, what)
db_file:write (string.format
(":%s %s %s %s :%s\n", who, "PRIVMSG", where, when, what))
end
function db_compact ()
db_file:close ()
-- Unfortunately, default Lua doesn't have anything like mkstemp()
local db_tmpname = db_filename .. "." .. os.time ()
db_file, e = io.open (db_tmpname, "a+")
if not db_file then error ("cannot save database: " .. e, 0) end
for who, places in pairs (db) do
for where, data in pairs (places) do
db_store (who, where, data[1], data[2])
end
end
db_file:flush ()
local ok, e = os.rename (db_tmpname, db_filename)
if not ok then error ("cannot save database: " .. e, 0) end
db_garbage = 0
end
for line in db_file:lines () do
local msg = parse (line)
remember (msg.prefix, table.unpack (msg.params))
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function seen (who, where, args)
local respond = function (...)
local privmsg = function (target, ...)
io.write ("PRIVMSG ", target, " :", table.concat { ... }, "\r\n")
end
if where:match ("^[#&!+]") then
privmsg (where, who, ": ", ...)
else
privmsg (who, ...)
end
end
local whom, e, garbage = args:match ("^(%S+)()%s*(.*)")
if not whom or #garbage ~= 0 then
return respond ("usage: <name>")
elseif who:lower () == whom:lower () then
return respond ("I can see you right now.")
end
local top = {}
-- That is, * acts like a wildcard, otherwise everything is escaped
local pattern = "^" .. whom:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
:gsub ("%*", ".*"):lower () .. "$"
for name, places in pairs (db) do
if places[where] and name:lower ():match (pattern) then
local when, what = table.unpack (places[where])
table.insert (top, { name = name, when = when, what = what })
end
end
if #top == 0 then
return respond ("I have not seen \x02" .. whom .. "\x02 here.")
end
-- Get all matching nicknames ordered from the most recently active
-- and make the list case insensitive (remove older duplicates)
table.sort (top, function (a, b) return a.when > b.when end)
for i = #top, 2, -1 do
if top[i - 1].name:lower () == top[i].name:lower () then
table.remove (top, i)
end
end
-- Hopefully the formatting mess will disrupt highlights in clients
for i = 1, math.min (#top, 3) do
local name = top[i].name:gsub ("^.", "%0\x02\x02")
respond (string.format ("\x02%s\x02 -> %s -> %s",
name, os.date ("%c", top[i].when), top[i].what))
end
end
function handle (msg)
local who = msg.prefix:match ("^[^!@]*")
local where, what = table.unpack (msg.params)
local when = os.time ()
local what_log = what:gsub ("^\x01ACTION", "*"):gsub ("\x01$", "")
remember (who, where, when, what_log)
db_store (who, where, when, what_log)
-- Comment out to reduce both disk load and reliability
db_file:flush ()
if db_garbage > 5000 then db_compact () end
if what:sub (1, #prefix) == prefix then
local command = what:sub (#prefix + 1)
local name, e = command:match ("^(%S+)%s*()")
if name == 'seen' then seen (who, where, command:sub (e)) end
end
end
for line in io.lines () do
local msg = parse (line)
if msg.command == "PRIVMSG" then handle (msg) end
end

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env perl
# Creates a database for the "seen" plugin from logs for degesch.
# The results may not be completely accurate but are good for jumpstarting.
# Usage: ./seen-import-degesch.pl LOG-FILE... > seen.db
use strict;
use warnings;
use File::Basename;
use Time::Piece;
my $db = {};
for (@ARGV) {
my $where = (basename($_) =~ /\.(.*).log/)[0];
unless ($where) {
print STDERR "Invalid filename: $_\n";
next;
}
open my $fh, '<', $_ or die "Failed to open log file: $!";
while (<$fh>) {
my ($when, $who, $who_action, $what) =
/^(.{19}) (?:<[~&@%+]*(.*?)>| \* (\S+)) (.*)/;
next unless $when;
if ($who_action) {
$who = $who_action;
$what = "* $what";
}
$db->{$who}->{$where} =
[Time::Piece->strptime($when, "%Y-%m-%d %T")->epoch, $what];
}
}
while (my ($who, $places) = each %$db) {
while (my ($where, $data) = each %$places) {
my ($when, $what) = @$data;
print ":$who PRIVMSG $where $when :$what\n";
}
}

View File

@@ -2,7 +2,7 @@
#
# ZyklonB YouTube plugin, displaying info about YouTube links
#
# Copyright 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
# Copyright 2014 - 2015, Přemysl Janouch <p@janouch.name>
# See the file LICENSE for licensing information.
#

14
test-static Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# We don't use printf's percent notation with our custom logging mechanism,
# so the compiler cannot check it for us like it usually does
perl -n0777 - "$(dirname "$0")"/degesch.c <<-'END'
while (/\blog_[^ ]+\s*\([^"()]*"[^"]*%[^%][^"]*"/gm) {
my ($p, $m) = ($`, $&);
printf "$ARGV:%d: suspicious log format string: %s...\n",
(1 + $p =~ tr/\n//), ($m =~ s/\s+/ /rg);
$status = 1;
}
END {
exit $status;
}
END

365
zyklonb.c
View File

@@ -1,11 +1,10 @@
/*
* zyklonb.c: the experimental IRC bot
*
* Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
* Copyright (c) 2014 - 2016, Přemysl Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -83,22 +82,22 @@ struct plugin
struct str write_buffer; ///< Output yet to be sent out
};
static void
plugin_init (struct plugin *self)
static struct plugin *
plugin_new (void)
{
memset (self, 0, sizeof *self);
struct plugin *self = xcalloc (1, sizeof *self);
self->pid = -1;
str_init (&self->queued_output);
self->queued_output = str_make ();
self->read_fd = -1;
str_init (&self->read_buffer);
self->read_buffer = str_make ();
self->write_fd = -1;
str_init (&self->write_buffer);
self->write_buffer = str_make ();
return self;
}
static void
plugin_free (struct plugin *self)
plugin_destroy (struct plugin *self)
{
soft_assert (self->pid == -1);
free (self->name);
@@ -113,6 +112,8 @@ plugin_free (struct plugin *self)
if (!self->initialized)
str_free (&self->queued_output);
free (self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -152,34 +153,33 @@ static void on_irc_reconnect_timeout (void *user_data);
static void
bot_context_init (struct bot_context *self)
{
str_map_init (&self->config);
self->config.free = free;
self->config = str_map_make (free);
simple_config_load_defaults (&self->config, g_config_table);
self->admin_re = NULL;
self->irc_fd = -1;
str_init (&self->read_buffer);
self->read_buffer = str_make ();
self->irc_registered = false;
self->ssl = NULL;
self->ssl_ctx = NULL;
self->plugins = NULL;
str_map_init (&self->plugins_by_name);
self->plugins_by_name = str_map_make (NULL);
poller_init (&self->poller);
self->quitting = false;
self->polling = false;
poller_timer_init (&self->timeout_tmr, &self->poller);
self->timeout_tmr = poller_timer_make (&self->poller);
self->timeout_tmr.dispatcher = on_irc_timeout;
self->timeout_tmr.user_data = self;
poller_timer_init (&self->ping_tmr, &self->poller);
self->ping_tmr = poller_timer_make (&self->poller);
self->ping_tmr.dispatcher = on_irc_ping_timeout;
self->ping_tmr.user_data = self;
poller_timer_init (&self->reconnect_tmr, &self->poller);
self->reconnect_tmr = poller_timer_make (&self->poller);
self->reconnect_tmr.dispatcher = on_irc_reconnect_timeout;
self->reconnect_tmr.user_data = self;
}
@@ -193,18 +193,13 @@ bot_context_free (struct bot_context *self)
str_free (&self->read_buffer);
// TODO: terminate the plugins properly before this is called
struct plugin *link, *tmp;
for (link = self->plugins; link; link = tmp)
{
tmp = link->next;
plugin_free (link);
free (link);
}
LIST_FOR_EACH (struct plugin, link, self->plugins)
plugin_destroy (link);
if (self->irc_fd != -1)
{
xclose (self->irc_fd);
poller_fd_reset (&self->irc_event);
xclose (self->irc_fd);
}
if (self->ssl)
SSL_free (self->ssl);
@@ -272,8 +267,7 @@ irc_send (struct bot_context *ctx, const char *format, ...)
return false;
va_start (ap, format);
struct str str;
str_init (&str);
struct str str = str_make ();
str_append_vprintf (&str, format, ap);
str_append (&str, "\r\n");
va_end (ap);
@@ -310,8 +304,54 @@ irc_get_boolean_from_config
if (set_boolean_if_valid (value, str))
return true;
error_set (e, "invalid configuration value for `%s'", name);
return false;
return error_set (e, "invalid configuration value for `%s'", name);
}
static bool
irc_initialize_ca_set (SSL_CTX *ssl_ctx, const char *file, const char *path,
struct error **e)
{
ERR_clear_error ();
if (file || path)
{
if (SSL_CTX_load_verify_locations (ssl_ctx, file, path))
return true;
return error_set (e, "%s: %s",
"failed to set locations for the CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
}
if (!SSL_CTX_set_default_verify_paths (ssl_ctx))
return error_set (e, "%s: %s",
"couldn't load the default CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
return true;
}
static bool
irc_initialize_ca (struct bot_context *ctx, struct error **e)
{
const char *ca_file = str_map_find (&ctx->config, "tls_ca_file");
const char *ca_path = str_map_find (&ctx->config, "tls_ca_path");
char *full_file = ca_file
? resolve_filename (ca_file, resolve_relative_config_filename) : NULL;
char *full_path = ca_path
? resolve_filename (ca_path, resolve_relative_config_filename) : NULL;
bool ok = false;
if (ca_file && !full_file)
error_set (e, "couldn't find the CA bundle file");
else if (ca_path && !full_path)
error_set (e, "couldn't find the CA bundle path");
else
ok = irc_initialize_ca_set (ctx->ssl_ctx, full_file, full_path, e);
free (full_file);
free (full_path);
return ok;
}
static bool
@@ -326,40 +366,19 @@ irc_initialize_ssl_ctx (struct bot_context *ctx, struct error **e)
SSL_CTX_set_verify (ctx->ssl_ctx,
verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
const char *ca_file = str_map_find (&ctx->config, "ca_file");
const char *ca_path = str_map_find (&ctx->config, "ca_path");
struct error *error = NULL;
if (ca_file || ca_path)
if (!irc_initialize_ca (ctx, &error))
{
if (SSL_CTX_load_verify_locations (ctx->ssl_ctx, ca_file, ca_path))
return true;
if (verify)
{
error_propagate (e, error);
return false;
}
error_set (&error, "%s: %s",
"failed to set locations for the CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
goto ca_error;
// Only inform the user if we're not actually verifying
print_warning ("%s", error->message);
error_free (error);
}
if (!SSL_CTX_set_default_verify_paths (ctx->ssl_ctx))
{
error_set (&error, "%s: %s",
"couldn't load the default CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
goto ca_error;
}
return true;
ca_error:
if (verify)
{
error_propagate (e, error);
return false;
}
// Only inform the user if we're not actually verifying
print_warning ("%s", error->message);
error_free (error);
return true;
}
@@ -419,8 +438,7 @@ error_ssl_1:
// multiple errors on the OpenSSL stack.
if (!error_info)
error_info = ERR_error_string (ERR_get_error (), NULL);
error_set (e, "%s: %s", "could not initialize TLS", error_info);
return false;
return error_set (e, "%s: %s", "could not initialize TLS", error_info);
}
static bool
@@ -433,11 +451,8 @@ irc_establish_connection (struct bot_context *ctx,
int err = getaddrinfo (host, port, &gai_hints, &gai_result);
if (err)
{
error_set (e, "%s: %s: %s",
"connection failed", "getaddrinfo", gai_strerror (err));
return false;
}
return error_set (e, "%s: %s: %s", "connection failed",
"getaddrinfo", gai_strerror (err));
int sockfd;
for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
@@ -478,10 +493,7 @@ irc_establish_connection (struct bot_context *ctx,
freeaddrinfo (gai_result);
if (!gai_iter)
{
error_set (e, "connection failed");
return false;
}
return error_set (e, "connection failed");
ctx->irc_fd = sockfd;
return true;
@@ -491,7 +503,7 @@ irc_establish_connection (struct bot_context *ctx,
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
static struct str_vector
static struct strv
g_original_argv, ///< Original program arguments
g_recovery_env; ///< Environment for re-exec recovery
@@ -636,6 +648,9 @@ recovery_handler (int signum, siginfo_t *info, void *context)
"signal received", signal_name);
*g_startup_reason_location = buf;
// Avoid annoying resource intensive infinite loops by sleeping for a bit
(void) sleep (1);
// TODO: maybe pregenerate the path, see the following for some other ways
// that would be illegal to do from within a signal handler:
// http://stackoverflow.com/a/1024937
@@ -663,8 +678,8 @@ recovery_handler (int signum, siginfo_t *info, void *context)
static void
prepare_recovery_environment (void)
{
str_vector_init (&g_recovery_env);
str_vector_add_vector (&g_recovery_env, environ);
g_recovery_env = strv_make ();
strv_append_vector (&g_recovery_env, environ);
// Prepare a location within the environment where we will put the startup
// (or maybe rather restart) reason in case of an irrecoverable error.
@@ -681,7 +696,7 @@ prepare_recovery_environment (void)
else
{
g_startup_reason_location = g_recovery_env.vector + g_recovery_env.len;
str_vector_add (&g_recovery_env, "");
strv_append (&g_recovery_env, "");
}
}
@@ -943,7 +958,7 @@ on_plugin_readable (const struct pollfd *fd, struct plugin *plugin)
struct str *buf = &plugin->read_buffer;
while (true)
{
str_ensure_space (buf, 512 + 1);
str_reserve (buf, 512 + 1);
ssize_t n_read = read (fd->fd, buf->str + buf->len,
buf->alloc - buf->len - 1);
@@ -1002,101 +1017,122 @@ is_valid_plugin_name (const char *name)
return true;
}
static bool
plugin_load (struct bot_context *ctx, const char *name, struct error **e)
static struct plugin *
plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
{
const char *plugin_dir = str_map_find (&ctx->config, "plugin_dir");
if (!plugin_dir)
{
error_set (e, "plugin directory not set");
return false;
}
if (!is_valid_plugin_name (name))
{
error_set (e, "invalid plugin name");
return false;
}
if (str_map_find (&ctx->plugins_by_name, name))
{
error_set (e, "the plugin has already been loaded");
return false;
return NULL;
}
int stdin_pipe[2];
if (pipe (stdin_pipe) == -1)
{
error_set (e, "%s: %s: %s",
"failed to load the plugin", "pipe", strerror (errno));
goto fail_1;
error_set (e, "%s: %s", "pipe", strerror (errno));
return NULL;
}
int stdout_pipe[2];
if (pipe (stdout_pipe) == -1)
{
error_set (e, "%s: %s: %s",
"failed to load the plugin", "pipe", strerror (errno));
goto fail_2;
error_set (e, "%s: %s", "pipe", strerror (errno));
goto fail_1;
}
struct str work_dir = str_make ();
get_xdg_home_dir (&work_dir, "XDG_DATA_HOME", ".local/share");
str_append_printf (&work_dir, "/%s", PROGRAM_NAME);
if (!mkdir_with_parents (work_dir.str, e))
goto fail_2;
set_cloexec (stdin_pipe[1]);
set_cloexec (stdout_pipe[0]);
pid_t pid = fork ();
if (pid == -1)
{
error_set (e, "%s: %s: %s",
"failed to load the plugin", "fork", strerror (errno));
goto fail_3;
error_set (e, "%s: %s", "fork", strerror (errno));
goto fail_2;
}
if (pid == 0)
{
// Redirect the child's stdin and stdout to the pipes
hard_assert (dup2 (stdin_pipe[0], STDIN_FILENO) != -1);
hard_assert (dup2 (stdout_pipe[1], STDOUT_FILENO) != -1);
if (dup2 (stdin_pipe[0], STDIN_FILENO) == -1
|| dup2 (stdout_pipe[1], STDOUT_FILENO) == -1)
{
print_error ("%s: %s: %s", "failed to load the plugin",
"dup2", strerror (errno));
_exit (EXIT_FAILURE);
}
if (chdir (work_dir.str))
{
print_error ("%s: %s: %s", "failed to load the plugin",
"chdir", strerror (errno));
_exit (EXIT_FAILURE);
}
xclose (stdin_pipe[0]);
xclose (stdout_pipe[1]);
struct str pathname;
str_init (&pathname);
str_append (&pathname, plugin_dir);
str_append_c (&pathname, '/');
str_append (&pathname, name);
// Restore some of the signal handling
signal (SIGPIPE, SIG_DFL);
char *const argv[] = { pathname.str, NULL };
char *argv[] = { xstrdup_printf ("%s/%s", plugin_dir, name), NULL };
execve (argv[0], argv, environ);
// We will collect the failure later via SIGCHLD
print_error ("%s: %s: %s",
"failed to load the plugin", "exec", strerror (errno));
print_error ("%s: %s: %s", "failed to load the plugin",
"exec", strerror (errno));
_exit (EXIT_FAILURE);
}
str_free (&work_dir);
xclose (stdin_pipe[0]);
xclose (stdout_pipe[1]);
set_blocking (stdout_pipe[0], false);
set_blocking (stdin_pipe[1], false);
struct plugin *plugin = xmalloc (sizeof *plugin);
plugin_init (plugin);
struct plugin *plugin = plugin_new ();
plugin->ctx = ctx;
plugin->pid = pid;
plugin->name = xstrdup (name);
plugin->read_fd = stdout_pipe[0];
plugin->write_fd = stdin_pipe[1];
return plugin;
poller_fd_init (&plugin->read_event, &ctx->poller, plugin->read_fd);
fail_2:
str_free (&work_dir);
xclose (stdout_pipe[0]);
xclose (stdout_pipe[1]);
fail_1:
xclose (stdin_pipe[0]);
xclose (stdin_pipe[1]);
return NULL;
}
static bool
plugin_load (struct bot_context *ctx, const char *name, struct error **e)
{
if (!is_valid_plugin_name (name))
return error_set (e, "invalid plugin name");
if (str_map_find (&ctx->plugins_by_name, name))
return error_set (e, "the plugin has already been loaded");
struct plugin *plugin;
if (!(plugin = plugin_launch (ctx, name, e)))
return false;
set_blocking (plugin->read_fd, false);
set_blocking (plugin->write_fd, false);
plugin->read_event = poller_fd_make (&ctx->poller, plugin->read_fd);
plugin->read_event.dispatcher = (poller_fd_fn) on_plugin_readable;
plugin->read_event.user_data = plugin;
poller_fd_init (&plugin->write_event, &ctx->poller, plugin->write_fd);
plugin->write_event = poller_fd_make (&ctx->poller, plugin->write_fd);
plugin->write_event.dispatcher = (poller_fd_fn) on_plugin_writable;
plugin->write_event.user_data = plugin;
@@ -1105,15 +1141,6 @@ plugin_load (struct bot_context *ctx, const char *name, struct error **e)
poller_fd_set (&plugin->read_event, POLLIN);
return true;
fail_3:
xclose (stdout_pipe[0]);
xclose (stdout_pipe[1]);
fail_2:
xclose (stdin_pipe[0]);
xclose (stdin_pipe[1]);
fail_1:
return false;
}
static bool
@@ -1122,10 +1149,7 @@ plugin_unload (struct bot_context *ctx, const char *name, struct error **e)
struct plugin *plugin = str_map_find (&ctx->plugins_by_name, name);
if (!plugin)
{
error_set (e, "no such plugin is loaded");
return false;
}
return error_set (e, "no such plugin is loaded");
plugin_zombify (plugin);
@@ -1141,10 +1165,8 @@ plugin_load_all_from_config (struct bot_context *ctx)
if (!plugin_list)
return;
struct str_vector plugins;
str_vector_init (&plugins);
cstr_split_ignore_empty (plugin_list, ',', &plugins);
struct strv plugins = strv_make ();
cstr_split (plugin_list, ",", true, &plugins);
for (size_t i = 0; i < plugins.len; i++)
{
char *name = cstr_strip_in_place (plugins.vector[i], " ");
@@ -1157,7 +1179,7 @@ plugin_load_all_from_config (struct bot_context *ctx)
}
}
str_vector_free (&plugins);
strv_free (&plugins);
}
// --- Main program ------------------------------------------------------------
@@ -1182,13 +1204,13 @@ parse_bot_command (const char *s, const char *command, const char **following)
}
static void
split_bot_command_argument_list (const char *arguments, struct str_vector *out)
split_bot_command_argument_list (const char *arguments, struct strv *out)
{
cstr_split_ignore_empty (arguments, ',', out);
cstr_split (arguments, ",", true, out);
for (size_t i = 0; i < out->len; )
{
if (!*cstr_strip_in_place (out->vector[i], " \t"))
str_vector_remove (out, i);
strv_remove (out, i);
else
i++;
}
@@ -1224,10 +1246,8 @@ respond_to_user (struct bot_context *ctx, const struct irc_message *msg,
strncpy (nick, msg->prefix, sizeof nick - 1);
nick[sizeof nick - 1] = '\0';
struct str text;
va_list ap;
str_init (&text);
struct str text = str_make ();
va_start (ap, format);
str_append_vprintf (&text, format, ap);
va_end (ap);
@@ -1288,9 +1308,7 @@ process_plugin_reload (struct bot_context *ctx,
static char *
make_status_report (struct bot_context *ctx)
{
struct str report;
str_init (&report);
struct str report = str_make ();
const char *reason = getenv (g_startup_reason_str);
if (!reason)
reason = "launched normally";
@@ -1335,8 +1353,7 @@ process_privmsg (struct bot_context *ctx, const struct irc_message *msg)
return;
const char *following;
struct str_vector list;
str_vector_init (&list);
struct strv list = strv_make ();
if (parse_bot_command (text, "quote", &following))
// This seems to replace tons of random stupid commands
@@ -1376,7 +1393,7 @@ process_privmsg (struct bot_context *ctx, const struct irc_message *msg)
process_plugin_unload (ctx, msg, list.vector[i]);
}
str_vector_free (&list);
strv_free (&list);
}
static void
@@ -1553,13 +1570,11 @@ on_irc_disconnected (struct bot_context *ctx)
ctx->ssl_ctx = NULL;
}
poller_fd_reset (&ctx->irc_event);
xclose (ctx->irc_fd);
ctx->irc_fd = -1;
ctx->irc_registered = false;
ctx->irc_event.closed = true;
poller_fd_reset (&ctx->irc_event);
// TODO: inform plugins about the disconnect event
// All of our timers have lost their meaning now
@@ -1614,7 +1629,7 @@ on_irc_readable (const struct pollfd *fd, struct bot_context *ctx)
bool disconnected = false;
while (true)
{
str_ensure_space (buf, 512);
str_reserve (buf, 512);
switch (fill_buffer (ctx, buf))
{
case IRC_READ_AGAIN:
@@ -1663,8 +1678,10 @@ struct irc_socks_data
};
static void
irc_on_socks_connected (void *user_data, int socket)
irc_on_socks_connected (void *user_data, int socket, const char *hostname)
{
(void) hostname;
struct irc_socks_data *data = user_data;
data->ctx->irc_fd = socket;
data->succeeded = true;
@@ -1722,6 +1739,8 @@ irc_establish_connection_socks (struct bot_context *ctx,
str_map_find (&ctx->config, "socks_password"));
while (data.polling)
poller_run (poller);
if (!data.succeeded)
error_set (e, "connection failed");
}
socks_connector_free (connector);
@@ -1750,10 +1769,7 @@ irc_connect (struct bot_context *ctx, struct error **e)
// TODO: again, get rid of `struct error' in here. The question is: how
// do we tell our caller that he should not try to reconnect?
if (!irc_host)
{
error_set (e, "no hostname specified in configuration");
return false;
}
return error_set (e, "no hostname specified in configuration");
bool use_tls;
if (!irc_get_boolean_from_config (ctx, "tls", &use_tls, e))
@@ -1774,7 +1790,7 @@ irc_connect (struct bot_context *ctx, struct error **e)
}
print_status ("connection established");
poller_fd_init (&ctx->irc_event, &ctx->poller, ctx->irc_fd);
ctx->irc_event = poller_fd_make (&ctx->poller, ctx->irc_fd);
ctx->irc_event.dispatcher = (poller_fd_fn) on_irc_readable;
ctx->irc_event.user_data = ctx;
@@ -1799,9 +1815,8 @@ parse_config (struct bot_context *ctx, struct error **e)
hard_assert (delay_str != NULL); // We have a default value for this
if (!xstrtoul (&ctx->reconnect_delay, delay_str, 10))
{
error_set (e, "invalid configuration value for `%s'",
"reconnect_delay");
return false;
return error_set (e,
"invalid configuration value for `%s'", "reconnect_delay");
}
hard_assert (!ctx->admin_re);
@@ -1856,8 +1871,7 @@ on_plugin_death (struct plugin *plugin, int status)
plugin->read_fd = -1;
LIST_UNLINK (ctx->plugins, plugin);
plugin_free (plugin);
free (plugin);
plugin_destroy (plugin);
// Living child processes block us from quitting
try_finish_quit (ctx);
@@ -1935,8 +1949,8 @@ on_signal_pipe_readable (const struct pollfd *fd, struct bot_context *ctx)
int
main (int argc, char *argv[])
{
str_vector_init (&g_original_argv);
str_vector_add_vector (&g_original_argv, argv);
g_original_argv = strv_make ();
strv_append_vector (&g_original_argv, argv);
static const struct opt opts[] =
{
@@ -1949,8 +1963,8 @@ main (int argc, char *argv[])
{ 0, NULL, NULL, 0, NULL }
};
struct opt_handler oh;
opt_handler_init (&oh, argc, argv, opts, NULL, "Experimental IRC bot.");
struct opt_handler oh =
opt_handler_make (argc, argv, opts, NULL, "Experimental IRC bot.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
@@ -1978,12 +1992,7 @@ main (int argc, char *argv[])
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
setup_signal_handlers ();
SSL_library_init ();
atexit (EVP_cleanup);
SSL_load_error_strings ();
// XXX: ERR_load_BIO_strings()? Anything else?
atexit (ERR_free_strings);
init_openssl ();
struct bot_context ctx;
bot_context_init (&ctx);
@@ -1997,11 +2006,17 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
poller_fd_init (&ctx.signal_event, &ctx.poller, g_signal_pipe[0]);
ctx.signal_event = poller_fd_make (&ctx.poller, g_signal_pipe[0]);
ctx.signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable;
ctx.signal_event.user_data = &ctx;
poller_fd_set (&ctx.signal_event, POLLIN);
#if OpenBSD >= 201605
// cpath is for creating the plugin home directory
if (pledge ("stdio rpath cpath inet proc exec", NULL))
exit_fatal ("%s: %s", "pledge", strerror (errno));
#endif
plugin_load_all_from_config (&ctx);
if (!parse_config (&ctx, &e)
|| !irc_connect (&ctx, &e))
@@ -2023,7 +2038,7 @@ main (int argc, char *argv[])
poller_run (&ctx.poller);
bot_context_free (&ctx);
str_vector_free (&g_original_argv);
strv_free (&g_original_argv);
return EXIT_SUCCESS;
}