source: code/icb.c@ bf02a60

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

add 'beep' and 'nobeep' support

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