1 /* Copyright (C) 2020 C. McEnroe <june@causal.agency>
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <https://www.gnu.org/licenses/>.
15 *
16 * Additional permission under GNU GPL version 3 section 7:
17 *
18 * If you modify this Program, or any covered work, by linking or
19 * combining it with OpenSSL (or a modified version of that library),
20 * containing parts covered by the terms of the OpenSSL License and the
21 * original SSLeay license, the licensors of this Program grant you
22 * additional permission to convey the resulting work. Corresponding
23 * Source for a non-source form of such a combination shall include the
24 * source code for the parts of OpenSSL used as well as that of the
25 * covered work.
26 */
27
28 #include <assert.h>
29 #include <ctype.h>
30 #include <err.h>
31 #include <getopt.h>
32 #include <stdarg.h>
33 #include <stdbool.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <sysexits.h>
39 #include <time.h>
40 #include <wchar.h>
41
42 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
43 #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
44
45 typedef unsigned uint;
46 typedef unsigned char byte;
47
48 static inline char *seprintf(char *ptr, char *end, const char *fmt, ...)
49 __attribute__((format(printf, 3, 4)));
seprintf(char * ptr,char * end,const char * fmt,...)50 static inline char *seprintf(char *ptr, char *end, const char *fmt, ...) {
51 va_list ap;
52 va_start(ap, fmt);
53 int n = vsnprintf(ptr, end - ptr, fmt, ap);
54 va_end(ap);
55 if (n < 0) return NULL;
56 if (n > end - ptr) return end;
57 return ptr + n;
58 }
59
60 enum Attr {
61 BIT(Bold),
62 BIT(Reverse),
63 BIT(Italic),
64 BIT(Underline),
65 };
66 enum Color {
67 White, Black, Blue, Green, Red, Brown, Magenta, Orange,
68 Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray,
69 Default = 99,
70 ColorCap,
71 };
72 struct Style {
73 enum Attr attr;
74 enum Color fg, bg;
75 };
76
77 static const struct Style StyleDefault = { 0, Default, Default };
78 enum { B = '\2', C = '\3', O = '\17', R = '\26', I = '\35', U = '\37' };
79
styleParse(struct Style * style,const char ** str)80 static inline size_t styleParse(struct Style *style, const char **str) {
81 switch (**str) {
82 break; case B: (*str)++; style->attr ^= Bold;
83 break; case O: (*str)++; *style = StyleDefault;
84 break; case R: (*str)++; style->attr ^= Reverse;
85 break; case I: (*str)++; style->attr ^= Italic;
86 break; case U: (*str)++; style->attr ^= Underline;
87 break; case C: {
88 (*str)++;
89 if (!isdigit(**str)) {
90 style->fg = Default;
91 style->bg = Default;
92 break;
93 }
94 style->fg = *(*str)++ - '0';
95 if (isdigit(**str)) style->fg = style->fg * 10 + *(*str)++ - '0';
96 if ((*str)[0] != ',' || !isdigit((*str)[1])) break;
97 (*str)++;
98 style->bg = *(*str)++ - '0';
99 if (isdigit(**str)) style->bg = style->bg * 10 + *(*str)++ - '0';
100 }
101 }
102 return strcspn(*str, (const char[]) { B, C, O, R, I, U, '\0' });
103 }
104
styleStrip(char * buf,size_t cap,const char * str)105 static inline void styleStrip(char *buf, size_t cap, const char *str) {
106 *buf = '\0';
107 char *ptr = buf, *end = &buf[cap];
108 struct Style style = StyleDefault;
109 while (*str) {
110 size_t len = styleParse(&style, &str);
111 ptr = seprintf(ptr, end, "%.*s", (int)len, str);
112 str += len;
113 }
114 }
115
116 enum { None, Debug, Network, IDCap = 256 };
117 extern char *idNames[IDCap];
118 extern enum Color idColors[IDCap];
119 extern uint idNext;
120
idFind(const char * name)121 static inline uint idFind(const char *name) {
122 for (uint id = 0; id < idNext; ++id) {
123 if (!strcasecmp(idNames[id], name)) return id;
124 }
125 return None;
126 }
127
idFor(const char * name)128 static inline uint idFor(const char *name) {
129 uint id = idFind(name);
130 if (id) return id;
131 if (idNext == IDCap) return Network;
132 idNames[idNext] = strdup(name);
133 idColors[idNext] = Default;
134 if (!idNames[idNext]) err(EX_OSERR, "strdup");
135 return idNext++;
136 }
137
138 extern uint32_t hashInit;
139 extern uint32_t hashBound;
_hash(const char * str)140 static inline uint32_t _hash(const char *str) {
141 if (*str == '~') str++;
142 uint32_t hash = hashInit;
143 for (; *str; ++str) {
144 hash = (hash << 5) | (hash >> 27);
145 hash ^= *str;
146 hash *= 0x27220A95;
147 }
148 return hash;
149 }
hash(const char * str)150 static inline enum Color hash(const char *str) {
151 if (hashBound < Blue) return Default;
152 return Blue + _hash(str) % (hashBound + 1 - Blue);
153 }
154
155 extern struct Network {
156 char *name;
157 uint userLen;
158 uint hostLen;
159 char *chanTypes;
160 char *statusmsg;
161 char *prefixes;
162 char *prefixModes;
163 char *listModes;
164 char *paramModes;
165 char *setParamModes;
166 char *channelModes;
167 char excepts;
168 char invex;
169 } network;
170
171 #define ENUM_CAP \
172 X("causal.agency/consumer", CapConsumer) \
173 X("chghost", CapChghost) \
174 X("extended-join", CapExtendedJoin) \
175 X("invite-notify", CapInviteNotify) \
176 X("message-tags", CapMessageTags) \
177 X("multi-prefix", CapMultiPrefix) \
178 X("sasl", CapSASL) \
179 X("server-time", CapServerTime) \
180 X("setname", CapSetname) \
181 X("userhost-in-names", CapUserhostInNames) \
182 X("znc.in/self-message", CapSelfMessage)
183
184 enum Cap {
185 #define X(name, id) BIT(id),
186 ENUM_CAP
187 #undef X
188 };
189
190 extern struct Self {
191 bool debug;
192 bool kiosk;
193 bool restricted;
194 size_t pos;
195 enum Cap caps;
196 char *plain;
197 char *mode;
198 char *join;
199 char *nick;
200 char *user;
201 char *host;
202 enum Color color;
203 char *invited;
204 char *quit;
205 } self;
206
set(char ** field,const char * value)207 static inline void set(char **field, const char *value) {
208 free(*field);
209 *field = strdup(value);
210 if (!*field) err(EX_OSERR, "strdup");
211 }
212
213 #define ENUM_TAG \
214 X("+draft/reply", TagReply) \
215 X("causal.agency/pos", TagPos) \
216 X("msgid", TagMsgID) \
217 X("time", TagTime)
218
219 enum Tag {
220 #define X(name, id) id,
221 ENUM_TAG
222 #undef X
223 TagCap,
224 };
225
226 enum { ParamCap = 254 };
227 struct Message {
228 char *tags[TagCap];
229 char *nick;
230 char *user;
231 char *host;
232 char *cmd;
233 char *params[ParamCap];
234 };
235
236 void ircConfig(
237 bool insecure, const char *trust, const char *cert, const char *priv
238 );
239 int ircConnect(const char *bind, const char *host, const char *port);
240 void ircHandshake(void);
241 void ircPrintCert(void);
242 void ircRecv(void);
243 void ircSend(const char *ptr, size_t len);
244 void ircFormat(const char *format, ...)
245 __attribute__((format(printf, 1, 2)));
246 void ircClose(void);
247
248 extern uint execID;
249 extern int execPipe[2];
250 extern int utilPipe[2];
251
252 enum { UtilCap = 16 };
253 struct Util {
254 uint argc;
255 const char *argv[UtilCap];
256 };
257
utilPush(struct Util * util,const char * arg)258 static inline void utilPush(struct Util *util, const char *arg) {
259 if (1 + util->argc < UtilCap) {
260 util->argv[util->argc++] = arg;
261 } else {
262 errx(EX_CONFIG, "too many utility arguments");
263 }
264 }
265
266 enum Reply {
267 ReplyAway = 1,
268 ReplyBan,
269 ReplyExcepts,
270 ReplyHelp,
271 ReplyInvex,
272 ReplyJoin,
273 ReplyList,
274 ReplyMode,
275 ReplyNames,
276 ReplyNamesAuto,
277 ReplyTopic,
278 ReplyTopicAuto,
279 ReplyWho,
280 ReplyWhois,
281 ReplyWhowas,
282 ReplyCap,
283 };
284
285 extern uint replies[ReplyCap];
286
287 void handle(struct Message *msg);
288 void command(uint id, char *input);
289 const char *commandIsPrivmsg(uint id, const char *input);
290 const char *commandIsNotice(uint id, const char *input);
291 const char *commandIsAction(uint id, const char *input);
292 size_t commandWillSplit(uint id, const char *input);
293 void commandCompleteAdd(void);
294
295 enum Heat { Ice, Cold, Warm, Hot };
296 enum { TimeCap = 64 };
297 extern enum Heat uiThreshold;
298 extern struct Time {
299 bool enable;
300 const char *format;
301 int width;
302 } uiTime;
303 extern struct Util uiNotifyUtil;
304 void uiInitEarly(void);
305 void uiInitLate(void);
306 void uiShow(void);
307 void uiHide(void);
308 void uiDraw(void);
309 void uiWindows(void);
310 void uiShowID(uint id);
311 void uiShowNum(uint num);
312 void uiMoveID(uint id, uint num);
313 void uiCloseID(uint id);
314 void uiCloseNum(uint id);
315 void uiRead(void);
316 void uiWrite(uint id, enum Heat heat, const time_t *time, const char *str);
317 void uiFormat(
318 uint id, enum Heat heat, const time_t *time, const char *format, ...
319 ) __attribute__((format(printf, 4, 5)));
320 void uiLoad(const char *name);
321 int uiSave(void);
322
323 enum { BufferCap = 1024 };
324 struct Buffer;
325 struct Line {
326 uint num;
327 enum Heat heat;
328 time_t time;
329 char *str;
330 };
331 struct Buffer *bufferAlloc(void);
332 void bufferFree(struct Buffer *buffer);
333 const struct Line *bufferSoft(const struct Buffer *buffer, size_t i);
334 const struct Line *bufferHard(const struct Buffer *buffer, size_t i);
335 int bufferPush(
336 struct Buffer *buffer, int cols, enum Heat thresh,
337 enum Heat heat, time_t time, const char *str
338 );
339 int bufferReflow(
340 struct Buffer *buffer, int cols, enum Heat thresh, size_t tail
341 );
342
343 enum Edit {
344 EditHead,
345 EditTail,
346 EditPrev,
347 EditNext,
348 EditPrevWord,
349 EditNextWord,
350 EditDeleteHead,
351 EditDeleteTail,
352 EditDeletePrev,
353 EditDeleteNext,
354 EditDeletePrevWord,
355 EditDeleteNextWord,
356 EditPaste,
357 EditTranspose,
358 EditCollapse,
359 EditInsert,
360 EditComplete,
361 EditExpand,
362 EditEnter,
363 };
364 void edit(uint id, enum Edit op, wchar_t ch);
365 char *editBuffer(size_t *pos);
366 void editCompleteAdd(void);
367
368 const char *complete(uint id, const char *prefix);
369 const char *completeSubstr(uint id, const char *substr);
370 void completeAccept(void);
371 void completeReject(void);
372 void completeAdd(uint id, const char *str, enum Color color);
373 void completeTouch(uint id, const char *str, enum Color color);
374 void completeReplace(uint id, const char *old, const char *new);
375 void completeRemove(uint id, const char *str);
376 void completeClear(uint id);
377 uint completeID(const char *str);
378 enum Color completeColor(uint id, const char *str);
379
380 extern struct Util urlOpenUtil;
381 extern struct Util urlCopyUtil;
382 void urlScan(uint id, const char *nick, const char *mesg);
383 void urlOpenCount(uint id, uint count);
384 void urlOpenMatch(uint id, const char *str);
385 void urlCopyMatch(uint id, const char *str);
386 int urlSave(FILE *file);
387 void urlLoad(FILE *file, size_t version);
388
389 enum { FilterCap = 64 };
390 extern struct Filter {
391 enum Heat heat;
392 char *mask;
393 char *cmd;
394 char *chan;
395 char *mesg;
396 } filters[FilterCap];
397 struct Filter filterParse(enum Heat heat, char *pattern);
398 struct Filter filterAdd(enum Heat heat, const char *pattern);
399 bool filterRemove(struct Filter filter);
400 enum Heat filterCheck(enum Heat heat, uint id, const struct Message *msg);
401
402 void logOpen(void);
403 void logFormat(uint id, const time_t *time, const char *format, ...)
404 __attribute__((format(printf, 3, 4)));
405 void logClose(void);
406
407 char *configPath(char *buf, size_t cap, const char *path, int i);
408 char *dataPath(char *buf, size_t cap, const char *path, int i);
409 FILE *configOpen(const char *path, const char *mode);
410 FILE *dataOpen(const char *path, const char *mode);
411
412 int getopt_config(
413 int argc, char *const *argv,
414 const char *optstring, const struct option *longopts, int *longindex
415 );
416