source: code/icb.c@ 8bf5a0a

Last change on this file since 8bf5a0a was 1da9ee5, checked in by Mike Belopuhov <mike.belopuhov@…>, 15 years ago

pysch pysch goto

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