source: code/icb.c@ c102bbf

Last change on this file since c102bbf was c102bbf, checked in by Florian Obser <florian@…>, 11 years ago

Make pidgin-icb /who and /msg work for real.

pidgin-icb seems to treat "." as the current group, so return the who
output for the group the icb session is connected to.
Turns out the previous commit doesn't fix anything - the tests were
wrong, so revert that.

  • Property mode set to 100644
File size: 14.9 KB
Line 
1/*
2 * Copyright (c) 2009, 2010, 2013, 2014 Mike Belopuhov
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <sys/param.h>
18#include <sys/queue.h>
19#include <stdarg.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <syslog.h>
24#include <unistd.h>
25#include <ctype.h>
26#include <event.h>
27#include <vis.h>
28
29#include "icb.h"
30#include "icbd.h"
31
32extern int creategroups;
33extern char srvname[MAXHOSTNAMELEN];
34
35void icb_command(struct icb_session *, char *, char *);
36void icb_groupmsg(struct icb_session *, char *);
37void icb_login(struct icb_session *, char *, char *, char *);
38int icb_dowho(struct icb_session *, struct icb_group *);
39char *icb_nextfield(char **);
40
41/*
42 * icb_init: initializes pointers to callbacks
43 */
44void
45icb_init(struct icbd_callbacks *ic)
46{
47 icb_drop = ic->drop;
48 icb_log = ic->log;
49 icb_send = ic->send;
50
51 LIST_INIT(&groups);
52
53 if (strlen(srvname) == 0)
54 (void)gethostname(srvname, sizeof srvname);
55 /*
56 * MAXHOSTNAMELEN is usually greater than what we
57 * can actually send, hence truncation:
58 */
59 if (strlen(srvname) > 200)
60 srvname[200] = '\0';
61}
62
63/*
64 * icb_start: called upon accepting a new connection, greets new client
65 */
66void
67icb_start(struct icb_session *is)
68{
69 icb_sendfmt(is, "%c%c%c%s%c%s", ICB_M_PROTO, '1', ICB_M_SEP, srvname,
70 ICB_M_SEP, "icbd");
71 SETF(is->flags, ICB_SF_PROTOSENT);
72}
73
74/*
75 * icb_input: main input processing routine
76 */
77void
78icb_input(struct icb_session *is)
79{
80 char *msg = is->buffer;
81 char type;
82
83 is->last = getmonotime();
84 type = msg[0];
85 msg++;
86 if (!ISSETF(is->flags, ICB_SF_LOGGEDIN) && type != ICB_M_LOGIN) {
87 icb_error(is, "Not logged in");
88 return;
89 }
90 switch (type) {
91 case ICB_M_LOGIN: {
92 char *nick, *group, *client, *cmd;
93
94 client = icb_nextfield(&msg);
95 nick = icb_nextfield(&msg);
96 group = icb_nextfield(&msg);
97 cmd = icb_nextfield(&msg);
98 if (strlen(cmd) > 0 && cmd[0] == 'w') {
99 icb_error(is, "Command not implemented");
100 icb_drop(is, NULL);
101 return;
102 }
103 if (strlen(cmd) == 0 || strcmp(cmd, "login") != 0) {
104 icb_error(is, "Malformed login packet");
105 icb_drop(is, NULL);
106 return;
107 }
108 icb_login(is, group, nick, client);
109 break;
110 }
111 case ICB_M_OPEN: {
112 char *grpmsg;
113
114 grpmsg = icb_nextfield(&msg);
115 icb_groupmsg(is, grpmsg);
116 break;
117 }
118 case ICB_M_COMMAND: {
119 char *cmd, *arg;
120
121 cmd = icb_nextfield(&msg);
122 arg = icb_nextfield(&msg);
123 icb_command(is, cmd, arg);
124 break;
125 }
126 case ICB_M_PONG: {
127 icb_sendfmt(is, "%c", ICB_M_PING);
128 break;
129 }
130 case ICB_M_PROTO:
131 case ICB_M_NOOP:
132 /* ignore */
133 break;
134 default:
135 /* everything else is not valid */
136 icb_error(is, "Bummer. This is a bummer, man.");
137 }
138}
139
140/*
141 * icb_login: handles login ('a') packets
142 */
143void
144icb_login(struct icb_session *is, char *grp, char *nick, char *client)
145{
146 char *defgrp = "1";
147 struct icb_group *ig;
148 struct icb_session *s;
149 char group[ICB_MAXGRPLEN];
150
151 if (!nick || strlen(nick) == 0 ||
152 icb_vis(is->nick, nick, ICB_MAXNICKLEN, VIS_SP)) {
153 icb_error(is, "Invalid nick");
154 icb_drop(is, NULL);
155 return;
156 }
157 if (!grp || strlen(grp) == 0)
158 strlcpy(group, defgrp, ICB_MAXGRPLEN);
159 else
160 icb_vis(group, grp, ICB_MAXNICKLEN, VIS_SP);
161 LIST_FOREACH(ig, &groups, entry) {
162 if (strcmp(ig->name, group) == 0)
163 break;
164 }
165 if (ig == NULL) {
166 if (!creategroups) {
167 icb_error(is, "Invalid group %s", group);
168 icb_drop(is, NULL);
169 return;
170 } else {
171 if ((ig = icb_addgroup(is, group, NULL)) == NULL) {
172 icb_error(is, "Can't create group %s", group);
173 return;
174 }
175 icb_log(NULL, LOG_DEBUG, "%s created group %s",
176 is->nick, group);
177 }
178 }
179 LIST_FOREACH(s, &ig->sess, entry) {
180 if (strcmp(s->nick, is->nick) == 0) {
181 icb_error(is, "Nick is already in use");
182 icb_drop(is, NULL);
183 return;
184 }
185 }
186
187 if (client && strlen(client) > 0)
188 icb_vis(is->client, client, sizeof is->client, VIS_SP);
189 is->group = ig;
190 is->login = time(NULL);
191 is->last = getmonotime();
192
193 /* notify group */
194 icb_status_group(ig, NULL, STATUS_SIGNON, "%s (%s@%s) entered group",
195 is->nick, is->client, is->host);
196
197 CLRF(is->flags, ICB_SF_PROTOSENT);
198 SETF(is->flags, ICB_SF_LOGGEDIN);
199
200 LIST_INSERT_HEAD(&ig->sess, is, entry);
201
202 /* acknowledge successful login */
203 icb_sendfmt(is, "%c", ICB_M_LOGIN);
204
205 /* notify user */
206 icb_status(is, STATUS_STATUS, "You are now in group %s%s", ig->name,
207 icb_ismod(ig, is) ? " as moderator" : "");
208
209 /* send user a topic name */
210 if (strlen(ig->topic) > 0)
211 icb_status(is, STATUS_TOPIC, "Topic for %s is \"%s\"",
212 ig->name, ig->topic);
213}
214
215/*
216 * icb_groupmsg: handles open message ('b') packets
217 */
218void
219icb_groupmsg(struct icb_session *is, char *msg)
220{
221 char buf[ICB_MSGSIZE];
222 struct icb_group *ig = is->group;
223 struct icb_session *s;
224 int buflen = 1;
225
226 if (strlen(msg) == 0) {
227 icb_error(is, "Empty message");
228 return;
229 }
230
231 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c%s", ICB_M_OPEN,
232 is->nick, ICB_M_SEP, msg);
233 buf[0] = buflen;
234
235 logger(ig->name, is->nick, msg);
236
237 LIST_FOREACH(s, &ig->sess, entry) {
238 if (s == is)
239 continue;
240 icb_send(s, buf, buflen + 1);
241 }
242}
243
244/*
245 * icb_privmsg: handles personal message ('c') packets
246 */
247void
248icb_privmsg(struct icb_session *is, char *to, char *msg)
249{
250 struct icb_group *ig = is->group;
251 struct icb_session *s;
252 char whom[ICB_MAXNICKLEN];
253
254 icb_vis(whom, to, ICB_MAXNICKLEN, VIS_SP);
255
256 /* try home group first */
257 LIST_FOREACH(s, &ig->sess, entry) {
258 if (strcmp(s->nick, whom) == 0)
259 break;
260 }
261 if (!s) {
262 /* try all groups until the first match */
263 LIST_FOREACH(ig, &groups, entry) {
264 LIST_FOREACH(s, &ig->sess, entry) {
265 if (strcmp(s->nick, whom) == 0)
266 break;
267 }
268 if (s)
269 break;
270 }
271 if (!s) {
272 icb_error(is, "No such user %s", whom);
273 return;
274 }
275 }
276 icb_sendfmt(s, "%c%s%c%s", ICB_M_PERSONAL, is->nick, ICB_M_SEP, msg);
277}
278
279/*
280 * icb_command: handles command ('h') packets
281 */
282void
283icb_command(struct icb_session *is, char *cmd, char *arg)
284{
285 void (*handler)(struct icb_session *, char *);
286 char command[32]; /* XXX */
287
288 icb_vis(command, cmd, sizeof command, VIS_SP);
289
290 if ((handler = icb_cmd_lookup(command)) == NULL) {
291 icb_error(is, "Unsupported command: %s", command);
292 return;
293 }
294 handler(is, arg);
295}
296
297/*
298 * icb_cmdout: sends out command output ('i') packets, called from the
299 * command handlers
300 */
301void
302icb_cmdout(struct icb_session *is, int type, char *outmsg)
303{
304 char *otype = NULL;
305
306 switch (type) {
307 case CMDOUT_CO:
308 otype = "co";
309 break;
310 case CMDOUT_EC:
311 otype = "ec";
312 break;
313 case CMDOUT_WG:
314 otype = "wg";
315 break;
316 case CMDOUT_WH:
317 otype = "wh";
318 break;
319 case CMDOUT_WL:
320 otype = "wl";
321 break;
322 default:
323 icb_log(is, LOG_ERR, "unknown cmdout type");
324 return;
325 }
326 if (outmsg)
327 icb_sendfmt(is, "%c%s%c%s", ICB_M_CMDOUT, otype, ICB_M_SEP,
328 outmsg);
329 else
330 icb_sendfmt(is, "%c%s", ICB_M_CMDOUT, otype);
331}
332
333/*
334 * icb_status: sends a status message ('d') to the client
335 */
336void
337icb_status(struct icb_session *is, int type, const char *fmt, ...)
338{
339 va_list ap;
340 char buf[ICB_MSGSIZE];
341 int buflen = 1;
342 static const struct {
343 int type;
344 const char *msg;
345 } msgtab[] = {
346 { STATUS_ARRIVE, "Arrive" },
347 { STATUS_BOOT, "Boot" },
348 { STATUS_DEPART, "Depart" },
349 { STATUS_HELP, "Help" },
350 { STATUS_NAME, "Name" },
351 { STATUS_NOBEEP, "No-Beep" },
352 { STATUS_NOTIFY, "Notify" },
353 { STATUS_SIGNON, "Sign-on" },
354 { STATUS_SIGNOFF, "Sign-off" },
355 { STATUS_STATUS, "Status" },
356 { STATUS_TOPIC, "Topic" },
357 { STATUS_WARNING, "Warning" },
358 { 0, NULL }
359 };
360
361 if (type < 0 || type > (int)nitems(msgtab) - 1)
362 return;
363 va_start(ap, fmt);
364 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c", ICB_M_STATUS,
365 msgtab[type].msg, ICB_M_SEP);
366 buflen += vsnprintf(&buf[buflen], sizeof buf - buflen, fmt, ap);
367 buf[0] = buflen;
368 va_end(ap);
369 icb_send(is, buf, buflen + 1);
370}
371
372/*
373 * icb_status: sends a status message ('d') to the group except of the
374 * "ex" if it's not NULL
375 */
376void
377icb_status_group(struct icb_group *ig, struct icb_session *ex, int type,
378 const char *fmt, ...)
379{
380 char buf[ICB_MSGSIZE - 10]; /* truncate to make sure all fits */
381 va_list ap;
382 struct icb_session *s;
383
384 va_start(ap, fmt);
385 (void)vsnprintf(buf, sizeof buf, fmt, ap);
386 LIST_FOREACH(s, &ig->sess, entry) {
387 if (ex && s == ex)
388 continue;
389 icb_status(s, type, buf);
390 }
391 logger(ig->name, "", buf);
392 icb_log(NULL, LOG_DEBUG, "%s", buf);
393 va_end(ap);
394}
395
396/*
397 * icb_error: sends an error message ('e') to the client
398 */
399void
400icb_error(struct icb_session *is, const char *fmt, ...)
401{
402 char buf[ICB_MSGSIZE];
403 va_list ap;
404 int buflen = 1;
405
406 va_start(ap, fmt);
407 buflen += vsnprintf(&buf[2], sizeof buf - 2, fmt, ap);
408 va_end(ap);
409 buf[0] = ++buflen; /* account for ICB_M_ERROR */
410 buf[1] = ICB_M_ERROR;
411 icb_send(is, buf, buflen + 1);
412 icb_log(is, LOG_DEBUG, "%s", buf + 2);
413}
414
415/*
416 * icb_remove: removes a session from the associated group
417 */
418void
419icb_remove(struct icb_session *is, char *reason)
420{
421 if (is->group) {
422 if (icb_ismod(is->group, is))
423 (void)icb_pass(is->group, is, NULL);
424 LIST_REMOVE(is, entry);
425 if (reason)
426 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
427 "%s (%s@%s) just left: %s", is->nick, is->client,
428 is->host, reason);
429 else
430 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
431 "%s (%s@%s) just left", is->nick, is->client,
432 is->host);
433 }
434}
435
436/*
437 * icb_addgroup: adds a new group to the list
438 */
439struct icb_group *
440icb_addgroup(struct icb_session *is, char *name, char *mpass)
441{
442 struct icb_group *ig;
443
444 if ((ig = calloc(1, sizeof *ig)) == NULL)
445 return (NULL);
446 strlcpy(ig->name, name, sizeof ig->name);
447 if (mpass)
448 strlcpy(ig->mpass, mpass, sizeof ig->mpass);
449 if (is)
450 ig->mod = is;
451 LIST_INIT(&ig->sess);
452 LIST_INSERT_HEAD(&groups, ig, entry);
453 return (ig);
454}
455
456#ifdef notused
457/*
458 * icb_delgroup: removes a group from the list
459 */
460void
461icb_delgroup(struct icb_group *ig)
462{
463 struct icb_session *s;
464
465 /* well, i guess we should kick out participants! ;-) */
466 LIST_FOREACH(s, &ig->sess, entry) {
467 icb_status(s, STATUS_WARNING, "Group dismissed");
468 s->group = NULL;
469 }
470 LIST_REMOVE(ig, entry);
471 bzero(ig, sizeof ig); /* paranoic thing, obviously */
472 free(ig);
473}
474#endif
475
476/*
477 * icb_dowho: a helper function that sends out a group header as a command
478 * output and user information in the "wl" format
479 */
480int
481icb_dowho(struct icb_session *is, struct icb_group *ig)
482{
483 char buf[ICB_MSGSIZE - 10]; /* truncate to make sure all fits */
484 struct icb_session *s;
485 int nusers = 0;
486
487 icb_cmdout(is, CMDOUT_CO, " ");
488 snprintf(buf, sizeof buf, "Group: %-8s (%cvl) Mod: %-13s Topic: %s",
489 ig->name, ig->mod ? 'm' : 'p', ig->mod ? ig->mod->nick : "(None)",
490 strlen(ig->topic) > 0 ? ig->topic : "(None)");
491 icb_cmdout(is, CMDOUT_CO, buf);
492 LIST_FOREACH(s, &ig->sess, entry) {
493 (void)snprintf(buf, sizeof buf,
494 "%c%c%s%c%lld%c0%c%lld%c%s%c%s%c%s",
495 icb_ismod(ig, s) ? 'm' : ' ', ICB_M_SEP,
496 s->nick, ICB_M_SEP, getmonotime() - s->last,
497 ICB_M_SEP, ICB_M_SEP, s->login, ICB_M_SEP,
498 s->client, ICB_M_SEP, s->host, ICB_M_SEP, " ");
499 icb_cmdout(is, CMDOUT_WL, buf);
500 nusers++;
501 }
502 return (nusers);
503}
504
505/*
506 * icb_who: sends a list of users of either the specified group or all
507 * groups found on the server
508 */
509void
510icb_who(struct icb_session *is, struct icb_group *ig)
511{
512 char buf[ICB_MSGSIZE - 10]; /* truncate to make sure all fits */
513 struct icb_group *g;
514
515 if (!ig) {
516 int nusers = 0, ngroups = 0;
517
518 LIST_FOREACH(g, &groups, entry) {
519 nusers += icb_dowho(is, g);
520 ngroups++;
521 }
522 if (nusers > 0) {
523 (void)snprintf(buf, sizeof buf,
524 "Total: %d %s in %d %s",
525 nusers, nusers > 1 ? "users" : "user",
526 ngroups, ngroups > 1 ? "groups" : "group");
527 } else
528 (void)snprintf(buf, sizeof buf, "No users found.");
529 icb_cmdout(is, CMDOUT_CO, buf);
530 } else
531 (void)icb_dowho(is, ig);
532}
533
534/*
535 * icb_ismod: checks whether group is moderated by "is"
536 */
537inline int
538icb_ismod(struct icb_group *ig, struct icb_session *is)
539{
540 return (ig->mod == is);
541}
542
543/*
544 * icb_modpermit: checks user against the moderators table if it has
545 * been populated
546 */
547int
548icb_modpermit(struct icb_session *is, int enforce)
549{
550 extern char modtab[ICB_MTABLEN][ICB_MAXNICKLEN];
551 extern int modtabcnt;
552
553 icbd_modupdate();
554 if ((enforce ? 0 : modtabcnt == 0) ||
555 bsearch(is->nick, modtab, modtabcnt, ICB_MAXNICKLEN,
556 (int (*)(const void *, const void *))strcmp))
557 return (1);
558 return (0);
559}
560
561/*
562 * icb_pass: passes moderation of group "ig" from "from" to "to",
563 * returns -1 if "from" is not a moderator, 1 if passed
564 * to "to" and 0 otherwise (no moderator or passed to the
565 * internal bot)
566 */
567int
568icb_pass(struct icb_group *ig, struct icb_session *from,
569 struct icb_session *to)
570{
571 if (ig->mod && ig->mod != from)
572 return (-1);
573 if (!from && !to)
574 return (-1);
575 ig->mod = to;
576 if (to)
577 icb_status(to, STATUS_NOTIFY, "%s just passed you moderation"
578 " of %s", from ? from->nick : "server", ig->name);
579 icb_status_group(ig, to, STATUS_NOTIFY, "%s has passed moderation "
580 "to %s", from ? from->nick : "server", to ? to->nick : "server");
581 return (1);
582}
583
584/*
585 * icb_nextfield: advances through a given buffer returning pointer to the
586 * beginning of the icb field or an empty string otherwise;
587 * cleans up trailing spaces
588 */
589char *
590icb_nextfield(char **buf)
591{
592 char *start = *buf;
593 char *end = NULL;
594
595 while (*buf && **buf != '\0' && **buf != ICB_M_SEP)
596 (*buf)++;
597 if (*buf && **buf == ICB_M_SEP) {
598 **buf = '\0';
599 end = *buf;
600 (*buf)++;
601 } else
602 end = *buf;
603 while (end && *(--end) == ' ' && end > start)
604 *end = '\0';
605 return (start);
606}
607
608/*
609 * icb_sendfmt: formats a string and sends it over
610 */
611void
612icb_sendfmt(struct icb_session *is, const char *fmt, ...)
613{
614 char buf[ICB_MSGSIZE];
615 va_list ap;
616 int buflen = 1;
617
618 va_start(ap, fmt);
619 buflen += vsnprintf(&buf[1], sizeof buf - 1, fmt, ap);
620 va_end(ap);
621 buf[0] = buflen;
622 icb_send(is, buf, buflen + 1);
623}
624
625/*
626 * icb_vis: strnvis-like function that escapes percentages as well
627 */
628int
629icb_vis(char *dst, const char *src, size_t dstsize, int flags)
630{
631 int si = 0, di = 0, td;
632
633 while ((size_t)di < dstsize && src[si] != '\0') {
634 if (src[si] == '%')
635 dst[di++] = '%', dst[di] = '%';
636 else if (src[si] == ' ' && flags & VIS_SP)
637 dst[di] = '_';
638 else if (isgraph(src[si]) || src[si] == ' ')
639 dst[di] = src[si];
640 else {
641 td = snprintf(&dst[di], dstsize - di,
642 "\\%03o", (unsigned char)src[si]);
643 di += td - 1;
644 }
645 si++, di++;
646 }
647 dst[MIN((size_t)di, dstsize)] = '\0';
648 return (0);
649}
Note: See TracBrowser for help on using the repository browser.