1 /* $Id: channels.c 448 2010-08-22 09:20:48Z tsaviran $
2  * -------------------------------------------------------
3  * Copyright (C) 2002-2006 Tommi Saviranta <wnd@iki.fi>
4  *	(C) 2002 Lee Hardy <lee@leeh.co.uk>
5  *	(C) 1998-2002 Sebastian Kienzl <zap@riot.org>
6  * -------------------------------------------------------
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif /* ifdef HAVE_CONFIG_H */
21 
22 #include "channels.h"
23 #include "client.h"
24 #include "common.h"
25 #include "qlog.h"
26 #include "irc.h"
27 #include "miau.h"
28 #include "messages.h"
29 #include "error.h"
30 #include "chanlog.h"
31 #include "automode.h"
32 
33 #include <string.h>
34 
35 
36 
37 /*
38  * active_channels
39  *	miau is on these channels
40  *
41  * passive_channels
42  *	miau will be on these channels.
43  *	For example, if cfg.leave == cfg.rejoin == true, active_channels are
44  *	moved to passive_channels when client detached.
45  *
46  * old_channels
47  *	miau was on these channels - and they still contain some
48  */
49 llist_list	active_channels;
50 llist_list	passive_channels;
51 llist_list	old_channels;
52 
53 
54 
55 /*
56  * Free data in channel_type -structure.
57  */
58 void
channel_free(channel_type * chan)59 channel_free(channel_type *chan)
60 {
61 	if (chan == NULL) {
62 #ifdef ENDUSERDEBUG
63 		enduserdebug("channel_free(NULL)");
64 #endif /* ifdef ENDUSERDEBUG */
65 		return;
66 	}
67 	if (chan->simple_name != chan->name) {
68 		xfree(chan->simple_name);
69 	}
70 	xfree(chan->name);
71 	xfree(chan->topic);
72 	xfree(chan->topicwhen);
73 	xfree(chan->topicwho);
74 	xfree(chan->key);
75 	xfree(chan);
76 } /* void channel_free(channel_type *chan) */
77 
78 
79 
80 /*
81  * Adds a channel to miaus internal list.
82  */
83 channel_type *
channel_add(const char * channel,const char * key,const int list)84 channel_add(const char *channel, const char *key, const int list)
85 {
86 	llist_list *target;
87 	channel_type *chptr;
88 
89 	/* See if channel is already on active list. */
90 	if (channel_find(channel, LIST_ACTIVE) != NULL) {
91 #ifdef ENDUSERDEBUG
92 		if (list == LIST_PASSIVE) {
93 			enduserdebug("add chan to pass when already on active");
94 		}
95 #endif /* ENDUSERDEBUG */
96 		return NULL;
97 	}
98 
99 	/*
100 	 * See if channel is already on passive list. If it is, it isn't
101 	 * a bad thing, tho.
102 	 */
103 	if (list == LIST_PASSIVE) {
104 		if (channel_find(channel, LIST_PASSIVE) != NULL) {
105 			return NULL;
106 		}
107 	}
108 
109 	/*
110 	 * We must do this to keep old GCC (and probably some other compilers
111 	 * too) happy.
112 	 *
113 	 * if (list == LIST_ACTIVE) {
114 	 *     either {
115 	 *         target = foo;
116 	 *     } or {
117 	 *         chptr == NULL;
118 	 *     }
119 	 * }
120 	 * if (chptr == NULL) {
121 	 *     target = bar;
122 	 * }
123 	 *
124 	 * Some compilers just don't get it!
125 	 */
126 	target = NULL;
127 
128 	/*
129 	 * See if we could simply move channels from passive/old_channels to
130 	 * active_channels. If we can do this, it's all we need to do.
131 	 */
132 	if (list == LIST_ACTIVE) {
133 		llist_list *source;
134 
135 		/* find a list to remove from */
136 		source = NULL;
137 		chptr = channel_find(channel, LIST_PASSIVE);
138 		if (chptr != NULL) {
139 			source = &passive_channels;
140 		} else {
141 			chptr = channel_find(channel, LIST_OLD);
142 			if (chptr != NULL) {
143 				source = &old_channels;
144 			}
145 		}
146 
147 		if (source != NULL) {
148 			llist_node *ptr;
149 			ptr = llist_find((void *) chptr, source);
150 			if (ptr != NULL) {
151 				llist_delete(ptr, source);
152 			}
153 #ifdef ENDUSERDEBUG
154 			else {
155 				enduserdebug("first the channel found, now it's not. what's going on!?");
156 			}
157 #endif /* ifdef ENDUSERDEBUG */
158 			target = &active_channels;
159 		}
160 	} else {
161 		chptr = NULL;
162 	}
163 
164 	/*
165 	 * Undernet doesn't echo channel password when password protected
166 	 * channel is joined. We would have to record the pass when used
167 	 * requests the channel. At the moment we don't do that, and
168 	 * automatically rejoining password protected channel will fail unless
169 	 * channel key was set while being on the channel and unless key was
170 	 * defined in "channels" option in miaurc.
171 	 */
172 
173 	/* Perhaps channel was not on passive/old_channels, after all. */
174 	if (chptr == NULL) {
175 		/* Create new node to channel list. */
176 		chptr = (channel_type *) xcalloc(1, sizeof(channel_type));
177 		chptr->name = xstrdup(channel);
178 		chptr->simple_name = chptr->name; /* assume to be the same... */
179 		chptr->simple_set = 0; /* ...but mark as not confirmed */
180 		chptr->name_set = 0; /* real name isn't known either */
181 		/*
182 		 * We don't need to touch other variabels - calloc did the
183 		 * job. This is neat, since a few values are set to 0 by
184 		 * default.
185 		 */
186 		chptr->key = xstrdup(key == NULL ? "-" : key);
187 		chptr->jointries = JOINTRIES_UNSET;
188 #ifdef AUTOMODE
189 		chptr->oper = -1;		/* Don't know our status. */
190 #endif /* AUTOMODE */
191 
192 		/* Get list on which to add this channel to. */
193 		if (list == LIST_ACTIVE) {
194 			target = &active_channels;
195 		} else if (list == LIST_PASSIVE) {
196 			target = &passive_channels;
197 		} else {
198 			target = &old_channels;
199 		}
200 	}
201 
202 	llist_add_tail(llist_create(chptr), target);
203 
204 	/*
205 	 * From now on, we know two things:
206 	 *   - channel is only on one list
207 	 *   - chptr is non-NULL
208 	 */
209 
210 	if (list == LIST_ACTIVE) {
211 		/* adding to ACTIVE -> we know real name of the channel */
212 		if (chptr->name_set == 0) {
213 			/* real name wasn't known before */
214 			xfree(chptr->name);
215 			chptr->name = xstrdup(channel);
216 			chptr->name_set = 1;
217 			if (chptr->simple_set == 0) {
218 				chptr->simple_name = chptr->name;
219 			}
220 		}
221 		if (chptr->simple_set == 0) {
222 			chptr->simple_name = channel_simplify_name(chptr->name);
223 			chptr->simple_set = 1;
224 		}
225 #ifdef CHANLOG
226 		/* active channels may need log-structures. */
227 		chanlog_open(chptr);
228 #endif /* CHANLOG */
229 	}
230 
231 	return chptr;
232 } /* channel_type *channel_add(const char *channel, const char *key,
233 		const int list) */
234 
235 
236 
237 /*
238  * Rmoves a channel from miaus internal list.
239  *
240  * "list" defines list hannel is to be removed from.
241  */
242 void
channel_rem(channel_type * chptr,const int list)243 channel_rem(channel_type *chptr, const int list)
244 {
245 	llist_list	*source;
246 	llist_node	*node;
247 
248 	if (list == LIST_ACTIVE) {
249 		source = &active_channels;
250 #ifdef QUICKLOG
251 		/* Need to know which active channels have qlog. */
252 		qlog_check(cfg.qloglength * 60);
253 #endif /* QUICKLOG */
254 	} else {
255 		source = (list == LIST_PASSIVE) ?
256 			&passive_channels : &old_channels;
257 	}
258 
259 	/* Only remove existing channels. :-) */
260 	if (chptr == NULL) {
261 #ifdef ENDUSERDEBUG
262 		enduserdebug("channel_rem(NULL, %d) called", list);
263 #endif /* ENDUSERDEBUG */
264 		return;
265 	}
266 
267 #ifdef AUTOMODE
268 	automode_drop_channel(chptr, NULL, ANY_MODE);
269 #endif /* AUTOMODE */
270 
271 #ifdef CHANLOG
272 	/* Close the logfile if we have one. */
273 	if (chptr->log != NULL) {
274 		chanlog_close(chptr);
275 	}
276 #endif /* CHANLOG */
277 
278 	node = llist_find(chptr, source);
279 	if (node == NULL) {
280 #ifdef ENDUSERDEBUG
281 		if (list == LIST_ACTIVE) {
282 			enduserdebug("removing non-existing channel");
283 		}
284 #endif /* ENDUSERDEBUG */
285 		return;
286 	}
287 
288 #ifdef QUICKLOG
289 	/* See if we need to move this channel to list of old channels. */
290 	if (chptr->hasqlog && list == LIST_ACTIVE) {
291 		/* Yep, we'll just move it. */
292 		llist_add_tail(llist_create(chptr), &old_channels);
293 	} else {
294 		/* No moving, just freeing the resources. */
295 		channel_free(chptr);
296 	}
297 #else /* QUICKLOG */
298 	/* Free resources. */
299 	channel_free(chptr);
300 #endif /* QUICKLOG */
301 
302 	/* And finally, remove channel from the list. */
303 	llist_delete(node, source);
304 } /* void channel_rem(channel_type *, const int) */
305 
306 
307 
308 /*
309  * Searches for a channel, returning it if found, else NULL.
310  *
311  * "list" declares which list to search.
312  */
313 channel_type *
channel_find(const char * name,int list)314 channel_find(const char *name, int list)
315 {
316 	llist_node *node;
317 	channel_type *chan;
318 	channel_type *bmatch;
319 	char *sname;
320 
321 	/* this means we won't work with new channel modes */
322 	if (channel_is_name(name) == 0) {
323 		return NULL;
324 	}
325 
326 	if (list == LIST_ACTIVE) {
327 		node = active_channels.head;
328 	} else if (list == LIST_PASSIVE) {
329 		node = passive_channels.head;
330 	} else {
331 		node = old_channels.head;
332 	}
333 
334 	sname = channel_simplify_name(name);
335 
336 	for (bmatch = NULL; node != NULL; node = node->next) {
337 		chan = (channel_type *) node->data;
338 
339 		if (xstrcasecmp(chan->name, name) == 0) {
340 			/* exact match */
341 			xfree(sname);
342 			return chan;
343 		} else if (xstrcasecmp(chan->simple_name, sname) == 0) {
344 			bmatch = chan;
345 		}
346 	}
347 
348 	xfree(sname);
349 	if (bmatch != NULL) {
350 		return bmatch;
351 	}
352 
353 	/* no match found */
354 	return NULL;
355 } /* channel_type *channel_find(char *name, int list) */
356 
357 
358 
359 /*
360  * Stores a topic for a channel we're in.
361  */
362 void
channel_topic(channel_type * chan,const char * topic)363 channel_topic(channel_type *chan, const char *topic)
364 {
365 	xfree(chan->topic);
366 	xfree(chan->topicwho);
367 	xfree(chan->topicwhen);
368 	chan->topic = NULL;
369 	chan->topicwho = NULL;
370 	chan->topicwhen = NULL;
371 
372 	if (topic != NULL && topic[0] != '\0') {
373 		chan->topic = xstrdup(topic);
374 	}
375 } /* void channel_topic(channel_type *chan, const char *topic) */
376 
377 
378 
379 /*
380  * Stores who set the topic for a channel we're in.
381  */
382 void
channel_when(channel_type * chan,const char * who,const char * when)383 channel_when(channel_type *chan, const char *who, const char *when)
384 {
385 	if (chan->topic != NULL) {
386 		xfree(chan->topicwho);
387 		xfree(chan->topicwhen);
388 
389 		chan->topicwho = xstrdup(who);
390 		chan->topicwhen = xstrdup(when);
391 	}
392 } /* void channel_when(channel_type *chan, const char *who, char *when) */
393 
394 
395 
396 void
channel_join_list(const int list,const int rejoin,connection_type * client)397 channel_join_list(const int list, const int rejoin, connection_type *client)
398 {
399 	llist_node *first;
400 	char *chans, *keys;
401 	size_t csize, ksize;
402 	size_t tclen, tklen;
403 	int try_joining;
404 
405 	if (list == LIST_PASSIVE) {
406 		first = passive_channels.head;
407 	} else {
408 		first = active_channels.head;
409 	}
410 	try_joining = 0;
411 
412 	/* Join old_channels if client was defined. */
413 	if (client != NULL) {
414 		LLIST_WALK_H(old_channels.head, channel_type *);
415 #ifdef QUICKLOG
416 			if (data->hasqlog) {
417 				irc_write(client, ":%s!%s JOIN :%s",
418 						status.nickname,
419 						status.idhostname,
420 						data->name);
421 			} else {
422 				channel_rem(data, LIST_OLD);
423 			}
424 #else /* ifdef QUICKLOG */
425 			channel_rem(data, LIST_OLD);
426 #endif /* ifdef else QUICKLOG */
427 		LLIST_WALK_F;
428 	}
429 
430 	if (first == NULL) {
431 		return;
432 	}
433 
434 	csize = ksize = 256;
435 	chans = (char *) xcalloc(csize, 1);
436 	keys = (char *) xcalloc(ksize, 1);
437 	tclen = tklen = 1;
438 
439 	LLIST_WALK_H(first, channel_type *);
440 		if (list == LIST_PASSIVE) {
441 			if ((rejoin == 1 || (cfg.rejoin == 1 && cfg.leave == 0))
442 					&& data->jointries == JOINTRIES_UNSET) {
443 				/*
444 				 * Set data->jointries to 1 even if
445 				 * cfg.jointries = 0. This way we can try to
446 				 * join channels in miaurc. If the channel
447 				 * is a safe channel, try joining only once.
448 				 */
449 				if (cfg.jointries == 0) {
450 					data->jointries = 1;
451 				} else {
452 					if (data->name[0] == '!') {
453 						data->jointries = 1;
454 					} else {
455 						data->jointries = cfg.jointries;
456 					}
457 				}
458 			}
459 
460 			if (data->jointries > 0) {
461 				size_t clen, klen;
462 				try_joining = 1;
463 				data->jointries--;
464 				/* Add channel and key in queue. */
465 				/*
466 				 * name and key guaranteed
467 				 * terminated and valid
468 				 *
469 				 * strncat == paranoid
470 				 */
471 				/*
472 				 * join with "simple" name, "real" name
473 				 * of a safe channel won't do here
474 				 */
475 				clen = strlen(data->name);
476 				klen = strlen(data->key);
477 				tclen += clen + 1;
478 				tklen += klen + 1;
479 				if (tclen > csize) {
480 					csize = tclen;
481 					chans = (char *) xrealloc(chans, csize);
482 				}
483 				if (tklen > ksize) {
484 					ksize = tklen;
485 					keys = (char *) xrealloc(keys, ksize);
486 				}
487 				strcat(chans, ",");
488 				strncat(chans, data->name, clen);
489 				strcat(keys, ",");
490 				strncat(keys, data->key, klen);
491 			} else if (data->jointries == 0) {
492 				channel_type *chan;
493 				/*
494 				 * If cfg.jointries is 0, remove channel from
495 				 * list after unsuccesfull attempt to join it.
496 				 */
497 				chan = channel_find(data->name, LIST_PASSIVE);
498 				channel_rem(chan, LIST_PASSIVE);
499 			}
500 		} else {
501 			/* Tell client to join this channel. */
502 			irc_write(client, ":%s!%s JOIN :%s",
503 					status.nickname,
504 					status.idhostname,
505 					data->name);
506 
507 			/* Also tell client about this channel. */
508 			irc_write(client, ":%s %d %s %s %s",
509 					i_server.realname,
510 					(data->topic != NULL ?
511 						RPL_TOPIC : RPL_NOTOPIC),
512 					status.nickname,
513 					data->name,
514 					(data->topic != NULL ?
515 						data->topic :
516 						":No topic is set"));
517 			if (data->topicwho != NULL && data->topicwhen != NULL) {
518 				irc_write(client, ":%s %d %s %s %s %s",
519 						i_server.realname,
520 						RPL_TOPICWHO,
521 						status.nickname,
522 						data->name,
523 						data->topicwho,
524 						data->topicwhen);
525 			}
526 #ifdef CNANLOG
527 			if (chanlog_has_log((channel_type *) data, LOG_MIAU)) {
528 				log_write_entry(data, LOGM_MIAU,
529 						get_short_localtime(), "");
530 			}
531 #endif /* CHANLOG */
532 			irc_write(&c_server, "NAMES %s", data->name);
533 		}
534 	LLIST_WALK_F;
535 
536 	if (try_joining == 1) {
537 		report(rejoin ? MIAU_REINTRODUCE : MIAU_JOINING, chans + 1);
538 		irc_write(&c_server, "JOIN %s %s", chans + 1, keys + 1);
539 	}
540 
541 	xfree(chans);
542 	xfree(keys);
543 } /* void channel_join_list(const int, const int, connection_type *) */
544 
545 
546 
547 char *
channel_simplify_name(const char * chan)548 channel_simplify_name(const char *chan)
549 {
550 	if (chan[0] != '!') {
551 		/* not safe-channel */
552 		return xstrdup(chan);
553 	} else {
554 		size_t len;
555 		char *name;
556 
557 		len = strlen(chan);
558 		if (len < 6) {
559 #ifdef ENDUSERDEBUG
560 			enduserdebug("weird channel: '%s'", chan);
561 #endif /* ifdef ENDUSERDEBUG */
562 			return xstrdup(chan); /* some weird channel */
563 		}
564 
565 		/* safe channel */
566 		len -= 4; /* original length - 5 + terminator */
567 		name = (char *) xmalloc(len);
568 		snprintf(name, len, "!%s", chan + 6);
569 		name[len - 1] = '\0';
570 		return name;
571 	}
572 } /* char *channel_simplify_name(const char *chan) */
573 
574 
575 
576 /*
577  * Check if name could be a channel name.
578  *
579  * Return non-zero if name could be a channel name.
580  */
581 int
channel_is_name(const char * name)582 channel_is_name(const char *name)
583 {
584 	if (name == NULL) {
585 		return 0;
586 	}
587 
588 	switch (name[0]) {
589 		case '#': /* ordinary channel (rfc 1459) */
590 		case '&': /* local channel (rfc 1459) */
591 		case '!': /* safe channel (rfc 2811) */
592 		case '+': /* modeless channel (rfc 2811) */
593 		case '.': /* programmable channel (kineircd) */
594 		case '~': /* global channel (what is that? kineircd) */
595 			return (int) name[0];
596 			break; /* dummy */
597 
598 		default:
599 			return 0;
600 			break; /* dummy */
601 	}
602 } /* int channel_is_name(const char *name) */
603 
604 
605 
606 #ifdef OBSOLETE /* Never defined - ignore this. */
607 static void
set_simple_name(channel_type * chan,const char * name)608 set_simple_name(channel_type *chan, const char *name)
609 {
610 	if (chan->name[0] != '!') {
611 		/* not safe-channel */
612 		chan->simple_set = 1;
613 	} else {
614 		if (strlen(chan->name) < 2 || strlen(name) < 6) {
615 			/* channel name too short -- not safe channel */
616 			return;
617 		}
618 		if (xstrcasecmp(chan->name + 2, name + 6) == 0) {
619 			chan->simplename = xstrdup(name);
620 			chan->simple_set = 1;
621 		}
622 	}
623 } /* static void set_simple_name(channel_type *chan, const char *name) */
624 
625 
626 
627 /*
628  * Creates a hash value based on the first 25 chars of a channel name.
629  */
630 unsigned int
channel_hash(char * p)631 channel_hash(char *p)
632 {
633 	int		i = 25;
634 	unsigned int	hash = 0;
635 
636 	while (*p && --i) {
637 		hash = (hash << 4) - (hash + (unsigned char) tolower(*p++));
638 	}
639 
640 	return (hash & (MAX_CHANNELS - 1));
641 } /* unsigned int channel_hash(char *) */
642 #endif /* OBSOLETE */
643