source: code/icb.c@ 9195a6a

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

add a simple help command

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