source: code/icb.c@ 7eb46d4

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

handle pong->ping

  • 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" },
317 { STATUS_NOTIFY, "Notify" },
318 { STATUS_SIGNON, "Sign-on" },
319 { STATUS_SIGNOFF, "Sign-off" },
320 { STATUS_STATUS, "Status" },
321 { STATUS_TOPIC, "Topic" },
322 { STATUS_WARNING, "Warning" },
[1022119]323 { 0, NULL }
[cd7b81d]324 };
325
326 if (type < 0 || type > (int)nitems(msgtab) - 1)
327 return;
328 va_start(ap, fmt);
329 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c", ICB_M_STATUS,
330 msgtab[type].msg, ICB_M_SEP);
331 buflen += vsnprintf(&buf[buflen], sizeof buf - buflen, fmt, ap);
332 buf[0] = buflen;
333 va_end(ap);
334 icb_send(is, buf, buflen + 1);
335}
336
337/*
338 * icb_status: sends a status message ('d') to the group except of the
339 * "ex" if it's not NULL
340 */
341void
342icb_status_group(struct icb_group *ig, struct icb_session *ex, int type,
343 const char *fmt, ...)
344{
345 char buf[ICB_MSGSIZE];
346 va_list ap;
347 struct icb_session *s;
348
349 va_start(ap, fmt);
350 (void)vsnprintf(buf, sizeof buf, fmt, ap);
351 LIST_FOREACH(s, &ig->sess, entry) {
352 if (ex && s == ex)
353 continue;
354 icb_status(s, type, buf);
355 }
356 icb_log(NULL, LOG_DEBUG, "%s", buf);
357 va_end(ap);
358}
359
360/*
361 * icb_error: sends an error message ('e') to the client
362 */
363void
364icb_error(struct icb_session *is, const char *fmt, ...)
365{
366 char buf[ICB_MSGSIZE];
367 va_list ap;
368 int buflen = 1;
369
370 va_start(ap, fmt);
371 buflen += vsnprintf(&buf[2], sizeof buf - 2, fmt, ap);
372 va_end(ap);
373 buf[0] = ++buflen; /* account for ICB_M_ERROR */
374 buf[1] = ICB_M_ERROR;
375 icb_send(is, buf, buflen + 1);
376 icb_log(is, LOG_DEBUG, "%s", buf + 2);
377}
378
379/*
380 * icb_remove: removes a session from the associated group
381 */
382void
383icb_remove(struct icb_session *is, char *reason)
384{
385 if (is->group) {
386 if (icb_ismoder(is->group, is))
387 (void)icb_pass(is->group, is, NULL);
388 LIST_REMOVE(is, entry);
389 if (reason)
390 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
391 "%s (%s@%s) just left: %s", is->nick, is->client,
392 is->host, reason);
393 else
394 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
395 "%s (%s@%s) just left", is->nick, is->client,
396 is->host);
397 }
398}
399
400/*
401 * icb_addgroup: adds a new group to the list
402 */
403struct icb_group *
404icb_addgroup(struct icb_session *is, char *name, char *mpass)
405{
406 struct icb_group *ig;
407
408 if ((ig = calloc(1, sizeof *ig)) == NULL)
409 return (NULL);
410 strlcpy(ig->name, name, sizeof ig->name);
411 if (mpass)
412 strlcpy(ig->mpass, mpass, sizeof ig->mpass);
413 if (is)
414 ig->moder = is;
415 LIST_INIT(&ig->sess);
416 LIST_INSERT_HEAD(&groups, ig, entry);
417 return (ig);
418}
419
420#ifdef notused
421/*
422 * icb_delgroup: removes a group from the list
423 */
424void
425icb_delgroup(struct icb_group *ig)
426{
427 struct icb_session *s;
428
429 /* well, i guess we should kick out participants! ;-) */
430 LIST_FOREACH(s, &ig->sess, entry) {
431 icb_status(s, STATUS_WARNING, "Group dismissed");
432 s->group = NULL;
433 }
434 LIST_REMOVE(ig, entry);
435 bzero(ig, sizeof ig); /* paranoic thing, obviously */
436 free(ig);
437}
438#endif
439
440/*
441 * icb_who: sends a list of users of the specified group (or the current
442 * one otherwise) in the "wl" format
443 */
444void
445icb_who(struct icb_session *is, struct icb_group *ig)
446{
447 char buf[ICB_MSGSIZE];
448 struct icb_session *s;
449
450 if (!ig)
451 ig = is->group;
452 LIST_FOREACH(s, &ig->sess, entry) {
453 (void)snprintf(buf, sizeof buf,
454 "%c%c%s%c%d%c0%c%d%c%s%c%s%c%s",
455 icb_ismoder(ig, s) ? '*' : ' ', ICB_M_SEP,
456 s->nick, ICB_M_SEP, getmonotime() - s->last,
457 ICB_M_SEP, ICB_M_SEP, s->login, ICB_M_SEP,
458 s->client, ICB_M_SEP, s->host, ICB_M_SEP, " ");
459 icb_cmdout(is, CMDOUT_WL, buf);
460 }
461}
462
463/*
464 * icb_ismoder: checks whether group is moderated by "is"
465 */
466int
467icb_ismoder(struct icb_group *ig, struct icb_session *is)
468{
469 if (ig->moder && ig->moder == is)
470 return (1);
471 return (0);
472}
473
474/*
475 * icb_pass: passes moderation of group "ig" from "from" to "to",
476 * returns -1 if "from" is not a moderator, 1 if passed
477 * to "to" and 0 otherwise (no moderator or passed to the
478 * internal bot)
479 */
480int
481icb_pass(struct icb_group *ig, struct icb_session *from,
482 struct icb_session *to)
483{
484 if (ig->moder && ig->moder != from)
485 return (-1);
486 if (!from && !to)
487 return (-1);
488 ig->moder = to;
489 if (to)
490 icb_status(to, STATUS_NOTIFY, "%s just passed you moderation"
491 " of %s", from ? from->nick : "server", ig->name);
492 icb_status_group(ig, to, STATUS_NOTIFY, "%s has passed moderation "
493 "to %s", from ? from->nick : "server", to ? to->nick : "server");
494 return (1);
495}
496
497/*
498 * icb_nextfield: advances through a given buffer returning pointer to
499 * the beginning of the icb field or an empty string otherwise
500 */
501char *
502icb_nextfield(char **buf)
503{
504 char *start = *buf;
505
506 while (*buf && **buf != '\0' && **buf != ICB_M_SEP)
507 (*buf)++;
508 if (*buf && **buf == ICB_M_SEP) {
509 **buf = '\0';
510 (*buf)++;
511 }
512 return (start);
513}
514
515/*
516 * icb_sendfmt: formats a string and sends it over
517 */
518void
519icb_sendfmt(struct icb_session *is, const char *fmt, ...)
520{
521 char buf[ICB_MSGSIZE];
522 va_list ap;
523 int buflen = 1;
524
525 va_start(ap, fmt);
526 buflen += vsnprintf(&buf[1], sizeof buf - 1, fmt, ap);
527 va_end(ap);
528 buf[0] = buflen;
529 icb_send(is, buf, buflen + 1);
530}
Note: See TracBrowser for help on using the repository browser.