source: code/icb.c@ 4284008

Last change on this file since 4284008 was 4284008, checked in by Mike Belopuhov <mike@…>, 11 years ago

Revamp "who" command handling; rename "moder" to "mod".

  • Property mode set to 100644
File size: 13.0 KB
Line 
1/*
2 * Copyright (c) 2009, 2010, 2013 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 <event.h>
26
27#include "icb.h"
28#include "icbd.h"
29
30extern int creategroups;
31extern char srvname[MAXHOSTNAMELEN];
32
33void icb_command(struct icb_session *, char *, char *);
34void icb_groupmsg(struct icb_session *, char *);
35void icb_login(struct icb_session *, char *, char *, char *);
36int icb_dowho(struct icb_session *, struct icb_group *);
37char *icb_nextfield(char **);
38
39/*
40 * icb_init: initializes pointers to callbacks
41 */
42void
43icb_init(struct icbd_callbacks *ic)
44{
45 icb_drop = ic->drop;
46 icb_log = ic->log;
47 icb_send = ic->send;
48
49 LIST_INIT(&groups);
50
51 if (strlen(srvname) == 0)
52 (void)gethostname(srvname, sizeof srvname);
53 /*
54 * MAXHOSTNAMELEN is usually greater than what we
55 * can actually send, hence truncation:
56 */
57 if (strlen(srvname) > 200)
58 srvname[200] = '\0';
59}
60
61/*
62 * icb_start: called upon accepting a new connection, greets new client
63 */
64void
65icb_start(struct icb_session *is)
66{
67 icb_sendfmt(is, "%c%c%c%s%c%s", ICB_M_PROTO, '1', ICB_M_SEP, srvname,
68 ICB_M_SEP, "icbd");
69 SETF(is->flags, ICB_SF_PROTOSENT);
70}
71
72/*
73 * icb_input: main input processing routine
74 */
75void
76icb_input(struct icb_session *is)
77{
78 char *msg = is->buffer;
79 char type;
80
81 is->last = getmonotime();
82 type = msg[1];
83 msg += 2;
84 if (!ISSETF(is->flags, ICB_SF_LOGGEDIN) && type != ICB_M_LOGIN) {
85 icb_error(is, "Not logged in");
86 return;
87 }
88 switch (type) {
89 case ICB_M_LOGIN: {
90 char *nick, *group, *client, *cmd;
91
92 client = icb_nextfield(&msg);
93 nick = icb_nextfield(&msg);
94 group = icb_nextfield(&msg);
95 cmd = icb_nextfield(&msg);
96 if (strlen(cmd) > 0 && cmd[0] == 'w') {
97 icb_error(is, "Command not implemented");
98 icb_drop(is, NULL);
99 return;
100 }
101 if (strlen(cmd) == 0 || strcmp(cmd, "login") != 0) {
102 icb_error(is, "Malformed login packet");
103 icb_drop(is, NULL);
104 return;
105 }
106 icb_login(is, group, nick, client);
107 break;
108 }
109 case ICB_M_OPEN: {
110 char *grpmsg;
111
112 grpmsg = icb_nextfield(&msg);
113 icb_groupmsg(is, grpmsg);
114 break;
115 }
116 case ICB_M_COMMAND: {
117 char *cmd, *arg;
118
119 cmd = icb_nextfield(&msg);
120 arg = icb_nextfield(&msg);
121 icb_command(is, cmd, arg);
122 break;
123 }
124 case ICB_M_PONG: {
125 icb_sendfmt(is, "%c", ICB_M_PING);
126 break;
127 }
128 case ICB_M_PROTO:
129 case ICB_M_NOOP:
130 /* ignore */
131 break;
132 default:
133 /* everything else is not valid */
134 icb_error(is, "Bummer. This is a bummer, man.");
135 }
136}
137
138/*
139 * icb_login: handles login ('a') packets
140 */
141void
142icb_login(struct icb_session *is, char *group, char *nick, char *client)
143{
144 char *defgrp = "1";
145 struct icb_group *ig;
146 struct icb_session *s;
147
148 if (!nick || strlen(nick) == 0) {
149 icb_error(is, "Invalid nick");
150 icb_drop(is, NULL);
151 return;
152 }
153 if (!group || strlen(group) == 0)
154 group = defgrp;
155 LIST_FOREACH(ig, &groups, entry) {
156 if (strcmp(ig->name, group) == 0)
157 break;
158 }
159 if (ig == NULL) {
160 if (!creategroups) {
161 icb_error(is, "Invalid group %s", group);
162 icb_drop(is, NULL);
163 return;
164 } else {
165 if ((ig = icb_addgroup(is, group, NULL)) == NULL) {
166 icb_error(is, "Can't create group %s", group);
167 return;
168 }
169 icb_log(NULL, LOG_DEBUG, "%s created group %s",
170 nick, group);
171 }
172 }
173 LIST_FOREACH(s, &ig->sess, entry) {
174 if (strcmp(s->nick, nick) == 0) {
175 icb_error(is, "Nick is already in use");
176 icb_drop(is, NULL);
177 return;
178 }
179 }
180
181 if (client && strlen(client) > 0)
182 strlcpy(is->client, client, sizeof is->client);
183 strlcpy(is->nick, nick, sizeof is->nick);
184 is->group = ig;
185 is->login = time(NULL);
186 is->last = getmonotime();
187
188 /* notify group */
189 icb_status_group(ig, NULL, STATUS_SIGNON, "%s (%s@%s) entered group",
190 is->nick, is->client, is->host);
191
192 CLRF(is->flags, ICB_SF_PROTOSENT);
193 SETF(is->flags, ICB_SF_LOGGEDIN);
194
195 LIST_INSERT_HEAD(&ig->sess, is, entry);
196
197 /* acknowledge successful login */
198 icb_sendfmt(is, "%c", ICB_M_LOGIN);
199
200 /* notify user */
201 icb_status(is, STATUS_STATUS, "You are now in group %s%s", ig->name,
202 icb_ismod(ig, is) ? " as moderator" : "");
203
204 /* send user a topic name */
205 if (strlen(ig->topic) > 0)
206 icb_status(is, STATUS_TOPIC, "Topic for %s is \"%s\"",
207 ig->name, ig->topic);
208}
209
210/*
211 * icb_groupmsg: handles open message ('b') packets
212 */
213void
214icb_groupmsg(struct icb_session *is, char *msg)
215{
216 char buf[ICB_MSGSIZE];
217 struct icb_group *ig = is->group;
218 struct icb_session *s;
219 int buflen = 1;
220
221 if (strlen(msg) == 0) {
222 icb_error(is, "Empty message");
223 return;
224 }
225
226 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c%s", ICB_M_OPEN,
227 is->nick, ICB_M_SEP, msg);
228 buf[0] = buflen;
229
230 LIST_FOREACH(s, &ig->sess, entry) {
231 if (s == is)
232 continue;
233 icb_send(s, buf, buflen + 1);
234 }
235}
236
237/*
238 * icb_privmsg: handles personal message ('c') packets
239 */
240void
241icb_privmsg(struct icb_session *is, char *whom, char *msg)
242{
243 struct icb_group *ig = is->group;
244 struct icb_session *s;
245
246 LIST_FOREACH(s, &ig->sess, entry) {
247 if (strcmp(s->nick, whom) == 0)
248 break;
249 }
250 if (!s) {
251 icb_error(is, "No such user %s", whom);
252 return;
253 }
254 icb_sendfmt(s, "%c%s%c%s", ICB_M_PERSONAL, is->nick, ICB_M_SEP, msg);
255}
256
257/*
258 * icb_command: handles command ('h') packets
259 */
260void
261icb_command(struct icb_session *is, char *cmd, char *arg)
262{
263 void (*handler)(struct icb_session *, char *);
264
265 if ((handler = icb_cmd_lookup(cmd)) == NULL) {
266 icb_error(is, "Unsupported command: %s", cmd);
267 return;
268 }
269 handler(is, arg);
270}
271
272/*
273 * icb_cmdout: sends out command output ('i') packets, called from the
274 * command handlers
275 */
276void
277icb_cmdout(struct icb_session *is, int type, char *outmsg)
278{
279 char *otype = NULL;
280
281 switch (type) {
282 case CMDOUT_CO:
283 otype = "co";
284 break;
285 case CMDOUT_EC:
286 otype = "ec";
287 break;
288 case CMDOUT_WG:
289 otype = "wg";
290 break;
291 case CMDOUT_WH:
292 otype = "wh";
293 break;
294 case CMDOUT_WL:
295 otype = "wl";
296 break;
297 default:
298 icb_log(is, LOG_ERR, "unknown cmdout type");
299 return;
300 }
301 if (outmsg)
302 icb_sendfmt(is, "%c%s%c%s", ICB_M_CMDOUT, otype, ICB_M_SEP,
303 outmsg);
304 else
305 icb_sendfmt(is, "%c%s", ICB_M_CMDOUT, otype);
306}
307
308/*
309 * icb_status: sends a status message ('d') to the client
310 */
311void
312icb_status(struct icb_session *is, int type, const char *fmt, ...)
313{
314 va_list ap;
315 char buf[ICB_MSGSIZE];
316 int buflen = 1;
317 static const struct {
318 int type;
319 const char *msg;
320 } msgtab[] = {
321 { STATUS_ARRIVE, "Arrive" },
322 { STATUS_BOOT, "Boot" },
323 { STATUS_DEPART, "Depart" },
324 { STATUS_HELP, "Help" },
325 { STATUS_NAME, "Name" },
326 { STATUS_NOBEEP, "No-Beep" },
327 { STATUS_NOTIFY, "Notify" },
328 { STATUS_SIGNON, "Sign-on" },
329 { STATUS_SIGNOFF, "Sign-off" },
330 { STATUS_STATUS, "Status" },
331 { STATUS_TOPIC, "Topic" },
332 { STATUS_WARNING, "Warning" },
333 { 0, NULL }
334 };
335
336 if (type < 0 || type > (int)nitems(msgtab) - 1)
337 return;
338 va_start(ap, fmt);
339 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c", ICB_M_STATUS,
340 msgtab[type].msg, ICB_M_SEP);
341 buflen += vsnprintf(&buf[buflen], sizeof buf - buflen, fmt, ap);
342 buf[0] = buflen;
343 va_end(ap);
344 icb_send(is, buf, buflen + 1);
345}
346
347/*
348 * icb_status: sends a status message ('d') to the group except of the
349 * "ex" if it's not NULL
350 */
351void
352icb_status_group(struct icb_group *ig, struct icb_session *ex, int type,
353 const char *fmt, ...)
354{
355 char buf[ICB_MSGSIZE];
356 va_list ap;
357 struct icb_session *s;
358
359 va_start(ap, fmt);
360 (void)vsnprintf(buf, sizeof buf, fmt, ap);
361 LIST_FOREACH(s, &ig->sess, entry) {
362 if (ex && s == ex)
363 continue;
364 icb_status(s, type, buf);
365 }
366 icb_log(NULL, LOG_DEBUG, "%s", buf);
367 va_end(ap);
368}
369
370/*
371 * icb_error: sends an error message ('e') to the client
372 */
373void
374icb_error(struct icb_session *is, const char *fmt, ...)
375{
376 char buf[ICB_MSGSIZE];
377 va_list ap;
378 int buflen = 1;
379
380 va_start(ap, fmt);
381 buflen += vsnprintf(&buf[2], sizeof buf - 2, fmt, ap);
382 va_end(ap);
383 buf[0] = ++buflen; /* account for ICB_M_ERROR */
384 buf[1] = ICB_M_ERROR;
385 icb_send(is, buf, buflen + 1);
386 icb_log(is, LOG_DEBUG, "%s", buf + 2);
387}
388
389/*
390 * icb_remove: removes a session from the associated group
391 */
392void
393icb_remove(struct icb_session *is, char *reason)
394{
395 if (is->group) {
396 if (icb_ismod(is->group, is))
397 (void)icb_pass(is->group, is, NULL);
398 LIST_REMOVE(is, entry);
399 if (reason)
400 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
401 "%s (%s@%s) just left: %s", is->nick, is->client,
402 is->host, reason);
403 else
404 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
405 "%s (%s@%s) just left", is->nick, is->client,
406 is->host);
407 }
408}
409
410/*
411 * icb_addgroup: adds a new group to the list
412 */
413struct icb_group *
414icb_addgroup(struct icb_session *is, char *name, char *mpass)
415{
416 struct icb_group *ig;
417
418 if ((ig = calloc(1, sizeof *ig)) == NULL)
419 return (NULL);
420 strlcpy(ig->name, name, sizeof ig->name);
421 if (mpass)
422 strlcpy(ig->mpass, mpass, sizeof ig->mpass);
423 if (is)
424 ig->mod = is;
425 LIST_INIT(&ig->sess);
426 LIST_INSERT_HEAD(&groups, ig, entry);
427 return (ig);
428}
429
430#ifdef notused
431/*
432 * icb_delgroup: removes a group from the list
433 */
434void
435icb_delgroup(struct icb_group *ig)
436{
437 struct icb_session *s;
438
439 /* well, i guess we should kick out participants! ;-) */
440 LIST_FOREACH(s, &ig->sess, entry) {
441 icb_status(s, STATUS_WARNING, "Group dismissed");
442 s->group = NULL;
443 }
444 LIST_REMOVE(ig, entry);
445 bzero(ig, sizeof ig); /* paranoic thing, obviously */
446 free(ig);
447}
448#endif
449
450/*
451 * icb_dowho: a helper function that sends out a group header as a command
452 * output and user information in the "wl" format
453 */
454int
455icb_dowho(struct icb_session *is, struct icb_group *ig)
456{
457 char buf[ICB_MSGSIZE];
458 struct icb_session *s;
459 int nusers = 0;
460
461 icb_cmdout(is, CMDOUT_CO, " ");
462 snprintf(buf, sizeof buf, "Group: %-8s (%cvl) Mod: %-13s Topic: %s",
463 ig->name, ig->mod ? 'm' : 'p', ig->mod ? ig->mod->nick : "(None)",
464 strlen(ig->topic) > 0 ? ig->topic : "(None)");
465 icb_cmdout(is, CMDOUT_CO, buf);
466 LIST_FOREACH(s, &ig->sess, entry) {
467 (void)snprintf(buf, sizeof buf,
468 "%c%c%s%c%lld%c0%c%lld%c%s%c%s%c%s",
469 icb_ismod(ig, s) ? 'm' : ' ', ICB_M_SEP,
470 s->nick, ICB_M_SEP, getmonotime() - s->last,
471 ICB_M_SEP, ICB_M_SEP, s->login, ICB_M_SEP,
472 s->client, ICB_M_SEP, s->host, ICB_M_SEP, " ");
473 icb_cmdout(is, CMDOUT_WL, buf);
474 nusers++;
475 }
476 return (nusers);
477}
478
479/*
480 * icb_who: sends a list of users of either the specified group or all
481 * groups found on the server
482 */
483void
484icb_who(struct icb_session *is, struct icb_group *ig)
485{
486 char buf[ICB_MSGSIZE];
487 struct icb_group *g;
488
489 if (!ig) {
490 int nusers = 0, ngroups = 0;
491
492 LIST_FOREACH(g, &groups, entry) {
493 nusers += icb_dowho(is, g);
494 ngroups++;
495 }
496 if (nusers > 0) {
497 (void)snprintf(buf, sizeof buf,
498 "Total: %d %s in %d %s",
499 nusers, nusers > 1 ? "users" : "user",
500 ngroups, ngroups > 1 ? "groups" : "group");
501 } else
502 (void)snprintf(buf, sizeof buf, "No users found.");
503 icb_cmdout(is, CMDOUT_CO, buf);
504 } else
505 (void)icb_dowho(is, ig);
506}
507
508/*
509 * icb_ismod: checks whether group is moderated by "is"
510 */
511int
512icb_ismod(struct icb_group *ig, struct icb_session *is)
513{
514 if (ig->mod && ig->mod == is)
515 return (1);
516 return (0);
517}
518
519/*
520 * icb_pass: passes moderation of group "ig" from "from" to "to",
521 * returns -1 if "from" is not a moderator, 1 if passed
522 * to "to" and 0 otherwise (no moderator or passed to the
523 * internal bot)
524 */
525int
526icb_pass(struct icb_group *ig, struct icb_session *from,
527 struct icb_session *to)
528{
529 if (ig->mod && ig->mod != from)
530 return (-1);
531 if (!from && !to)
532 return (-1);
533 ig->mod = to;
534 if (to)
535 icb_status(to, STATUS_NOTIFY, "%s just passed you moderation"
536 " of %s", from ? from->nick : "server", ig->name);
537 icb_status_group(ig, to, STATUS_NOTIFY, "%s has passed moderation "
538 "to %s", from ? from->nick : "server", to ? to->nick : "server");
539 return (1);
540}
541
542/*
543 * icb_nextfield: advances through a given buffer returning pointer to the
544 * beginning of the icb field or an empty string otherwise
545 */
546char *
547icb_nextfield(char **buf)
548{
549 char *start = *buf;
550
551 while (*buf && **buf != '\0' && **buf != ICB_M_SEP)
552 (*buf)++;
553 if (*buf && **buf == ICB_M_SEP) {
554 **buf = '\0';
555 (*buf)++;
556 }
557 return (start);
558}
559
560/*
561 * icb_sendfmt: formats a string and sends it over
562 */
563void
564icb_sendfmt(struct icb_session *is, const char *fmt, ...)
565{
566 char buf[ICB_MSGSIZE];
567 va_list ap;
568 int buflen = 1;
569
570 va_start(ap, fmt);
571 buflen += vsnprintf(&buf[1], sizeof buf - 1, fmt, ap);
572 va_end(ap);
573 buf[0] = buflen;
574 icb_send(is, buf, buflen + 1);
575}
Note: See TracBrowser for help on using the repository browser.