1 /* IRCClient.c - TransparentIRC 0.1
2 * Copyright (C) 2011-2012 Philipp Kreil (pk910)
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "IRCClient.h"
18 #include "IOHandler.h"
19 #include "UserSession.h"
20 #include "UserClient.h"
22 #include "ConfigParser.h"
24 static void ircclient_callback(struct IOEvent *event);
26 void ircclient_initialize(struct UserSession *session, struct UserLogin *login) {
27 struct IRCClient *client = calloc(1, sizeof(*client));
29 usersession_error(session, "Could not initialize IRC connection.");
32 client->session = session;
33 session->irc = client;
35 char *server_address = (login->server_address ? login->server_address : get_string_field("server.host"));
36 int server_port = (login->server_override_port ? login->server_port : get_int_field("server.port"));
37 int server_ssl = (login->server_override_port ? login->server_ssl : get_int_field("server.ssl"));
38 char *bind_address = (login->bind_address ? login->bind_address : get_string_field("server.bind"));
40 char notification[LINELEN];
41 sprintf(notification, "Connecting to %s (%s%d)", server_address, (server_ssl ? "+" : ""), server_port);
42 usersession_client_notification(session, notification);
44 client->iofd = iohandler_connect(server_address, server_port, server_ssl, bind_address, ircclient_callback);
46 usersession_error(session, "Could not initialize IRC connection.");
49 client->iofd->data = client;
52 void ircclient_close(struct IRCClient *client) {
53 client->session->irc = NULL;
54 iohandler_printf(client->iofd, "QUIT :[TransparentIRC] Quit");
55 iohandler_close(client->iofd);
56 struct IRCLine *recover_line, *next_line;
57 for(recover_line = client->recover_header; recover_line; recover_line = next_line) {
58 next_line = recover_line->next;
59 free(recover_line->line);
62 if(client->network_prefixes)
63 free(client->network_prefixes);
64 if(client->network_prefixes_char)
65 free(client->network_prefixes_char);
66 if(client->network_chanmodes)
67 free(client->network_chanmodes);
68 if(client->network_chantypes)
69 free(client->network_chantypes);
73 static void ircclient_handshake(struct IRCClient *client) {
74 struct UserSession *session = client->session;
75 struct IODescriptor *iofd = client->iofd;
76 iohandler_printf(iofd, "PASS %s:%s", session->username, session->password);
77 iohandler_printf(iofd, "NICK %s", session->nick);
78 iohandler_printf(iofd, "USER %s 0 * :%s", session->username, session->realname);
79 free(session->realname);
80 session->realname = NULL;
83 static struct IRCUser ircclient_parse_user(char *from) {
85 char *a = from, *b = from;
98 while(*b && *b != '@') {
112 static void ircclient_userlist_clear(struct IRCChannel *channel) {
113 struct IRCChannelMember *member, *next_member;
114 for(member = channel->userlist; member; member = next_member) {
115 next_member = member->next;
119 channel->userlist = NULL;
122 static struct IRCChannelMember *ircclient_userlist_add(struct IRCChannel *channel, char *nick) {
123 struct IRCChannelMember *member;
124 for(member = channel->userlist; member; member = member->next) {
125 if(!stricmp(member->nick, nick)) return member; //prevent duplicates
127 member = calloc(1, sizeof(*member));
128 member->nick = strdup(nick);
129 member->next = channel->userlist;
130 if(channel->userlist)
131 channel->userlist->prev = member;
132 channel->userlist = member;
136 static void ircclient_userlist_del(struct IRCChannel *channel, char *nick) {
137 struct IRCChannelMember *member;
138 for(member = channel->userlist; member; member = member->next) {
139 if(!stricmp(member->nick, nick)) break;
143 member->prev->next = member->next;
145 channel->userlist = member->next;
147 member->next->prev = member->prev;
152 static void ircclient_raw005_parse(struct IRCClient *client, char **argv, int argc) {
156 for(i = 0; i < argc; i++) {
158 value = strchr(argv[i], '=');
163 if(!stricmp(name, "PREFIX")) {
165 if(*value != '(') continue;
167 char *prefixes_char = value;
168 value = strchr(value, ')');
172 if(strlen(prefixes_char) == strlen(value)) {
173 client->network_prefixes = strdup(value);
174 client->network_prefixes_char = strdup(prefixes_char);
180 static void ircclient_chanmode_parse(struct IRCClient *client, struct IRCChannel *channel, char *mode, char **argv, int argc) {
185 struct IRCChannelMember *member;
189 else if(*mode == '-')
192 char *prefix_chars = client->network_prefixes_char ? client->network_prefixes_char : "ov";
194 while(prefix_chars[i]) {
195 if(prefix_chars[i] == *mode) {
197 member = ircclient_userlist_add(channel, carg);
199 member->modes |= (1 << i);
201 member->modes &= ~(1 << i);
202 goto ircclient_chanmode_parse_next;
206 char *chanmodes = client->network_chanmodes ? client->network_chanmodes : "b,k,l,";
209 while(chanmodes[i]) {
210 if(chanmodes[i] == ',') {
212 if(j == 2 && !add) break;
215 if(chanmodes[i] == *mode) {
217 goto ircclient_chanmode_parse_next;
220 //default mode without parameter...
222 ircclient_chanmode_parse_next:
227 static int ircclient_is_channel_prefix(struct IRCClient *client, char prefix) {
228 if(!client->network_chantypes) {
229 return (prefix == '#'); //default
232 while(client->network_chantypes[i]) {
233 if(prefix == client->network_chantypes[i])
240 static int ircclient_is_chanuser_prefix(struct IRCClient *client, char prefix, int *prefix_id) {
241 char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
243 for(i = 0; prefixes[i]; i++) {
244 if(prefixes[i] == prefix) {
252 static int ircclient_build_chanuser_prefix(struct IRCClient *client, struct IRCChannelMember *member, char *buffer, int highest_only) {
253 char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
256 for(i = 0; prefixes[i]; i++) {
257 if(member->modes & (1 << i)) {
258 buffer[j++] = prefixes[i];
259 if(highest_only) break;
266 static void ircclient_recv(struct IRCClient *client, char *line) {
267 struct UserSession *session = client->session;
268 char *argv[MAXNUMPARAMS];
271 int argc = parse_line(tmp, argv, 1);
272 int pass_to_client = 1;
273 if(argc > 2 && !stricmp(argv[1], "PING")) {
274 iohandler_printf(client->iofd, "PONG :%s", argv[2]);
277 if(!client->auth_confirmed) {
278 if(argc == 4 && !stricmp(argv[1], "NOTICE") && !stricmp(argv[2], "AUTH") && !stricmp(argv[3], "*** Login accepted")) {
279 client->auth_confirmed = 1;
280 } else if(argc > 1 && !stricmp(argv[1], "001")) {
281 usersession_error(session, "IRC Session closed (Please use Login on Connect)");
285 if(!stricmp(argv[1], "001")) {
287 session->nick = strdup(argv[2]);
288 client->fully_connected = 1;
290 if(!stricmp(argv[1], "005")) {
292 ircclient_raw005_parse(client, argv+3, argc-3);
294 if(!stricmp(argv[1], "001") ||
295 !stricmp(argv[1], "002") ||
296 !stricmp(argv[1], "003") ||
297 !stricmp(argv[1], "004") ||
298 !stricmp(argv[1], "005") ||
299 !stricmp(argv[1], "375") ||
300 !stricmp(argv[1], "372") ||
301 !stricmp(argv[1], "376")
303 //save these raw's for recovering the connection later
304 struct IRCLine *recover_line = NULL, *new_line;
305 if(client->recover_header)
306 for(recover_line = client->recover_header; recover_line->next; recover_line = recover_line->next) {};
307 new_line = malloc(sizeof(*new_line));
309 new_line->line = strdup(line);
310 new_line->next = NULL;
312 recover_line->next = new_line;
314 client->recover_header = new_line;
316 } else if(!stricmp(argv[1], "JOIN")) {
317 struct IRCUser from = ircclient_parse_user(argv[0]);
318 struct IRCChannel *channel;
319 if(!stricmp(from.nick, session->nick)) {
320 struct IRCChannel *lchannel = NULL;
321 channel = calloc(1, sizeof(*channel));
323 for(lchannel = client->channel; lchannel->next; lchannel = lchannel->next) {}
324 channel->name = strdup(argv[2]);
325 channel->prev = lchannel;
327 lchannel->next = channel;
329 client->channel = channel;
331 for(channel = client->channel; channel; channel = channel->next) {
332 if(!stricmp(channel->name, argv[2])) {
333 ircclient_userlist_add(channel, from.nick);
338 } else if(!stricmp(argv[1], "PART")) {
339 struct IRCUser from = ircclient_parse_user(argv[0]);
340 struct IRCChannel *channel;
341 for(channel = client->channel; channel; channel = channel->next) {
342 if(!stricmp(channel->name, argv[2])) {
343 if(!stricmp(from.nick, session->nick)) {
344 ircclient_userlist_clear(channel);
346 channel->prev->next = channel->next;
348 client->channel = channel->next;
350 channel->next->prev = channel->prev;
354 ircclient_userlist_del(channel, from.nick);
359 } else if(!stricmp(argv[1], "KICK")) {
360 struct IRCChannel *channel;
361 for(channel = client->channel; channel; channel = channel->next) {
362 if(!stricmp(channel->name, argv[2])) {
363 if(!stricmp(argv[3], session->nick)) {
364 ircclient_userlist_clear(channel);
366 channel->prev->next = channel->next;
368 client->channel = channel->next;
370 channel->next->prev = channel->prev;
374 ircclient_userlist_del(channel, argv[3]);
379 } else if(!stricmp(argv[1], "NICK")) {
380 struct IRCUser from = ircclient_parse_user(argv[0]);
381 if(!stricmp(from.nick, session->nick)) {
383 session->nick = strdup(argv[2]);
385 struct IRCChannel *channel;
386 struct IRCChannelMember *member;
387 for(channel = client->channel; channel; channel = channel->next) {
388 for(member = channel->userlist; member; member = member->next) {
389 if(!stricmp(member->nick, from.nick)) {
391 member->nick = strdup(argv[2]);
396 } else if(!stricmp(argv[1], "MODE")) {
397 if(!stricmp(argv[2], session->nick)) {
399 } else if(ircclient_is_channel_prefix(client, argv[2][0])) {
400 struct IRCChannel *channel;
401 struct IRCChannelMember *member;
402 for(channel = client->channel; channel; channel = channel->next) {
403 for(member = channel->userlist; member; member = member->next) {
404 ircclient_chanmode_parse(client, channel, argv[3], argv+4, argc-4);
408 } else if(!stricmp(argv[1], "353")) {
409 struct IRCChannel *channel;
410 struct IRCChannelMember *member;
411 for(channel = client->channel; channel; channel = channel->next) {
412 if(!stricmp(channel->name, argv[4])) {
413 if(!channel->synchronizing_userlist) {
414 channel->synchronizing_userlist = 1;
415 ircclient_userlist_clear(channel);
422 if((c = strchr(a, '!'))) {
426 int member_modes = 0;
427 while(ircclient_is_chanuser_prefix(client, *a, &prefix_id)) {
428 member_modes |= (1 << prefix_id);
431 member = ircclient_userlist_add(channel, a);
432 member->modes = member_modes;
438 } else if(!stricmp(argv[1], "366")) {
439 struct IRCChannel *channel;
440 for(channel = client->channel; channel; channel = channel->next) {
441 if(!stricmp(channel->name, argv[3])) {
442 channel->synchronizing_userlist = 0;
448 usersession_client_raw(session, line);
451 static void ircclient_callback(struct IOEvent *event) {
452 struct IRCClient *client = event->iofd->data;
453 switch(event->type) {
454 case IOEVENT_NOTCONNECTED:
455 usersession_error(client->session, "Could not connect to the Server.");
457 case IOEVENT_CONNECTED:
458 ircclient_handshake(client);
461 ircclient_recv(client, event->data.recv_str);
464 usersession_error(client->session, "Disconnected from the IRC Server.");
471 void ircclient_send(struct IRCClient *client, char *line) {
472 iohandler_printf(client->iofd, "%s", line);
475 void ircclient_recover_session(struct UserSession *session) {
476 struct IRCClient *client = session->irc;
478 struct IRCLine *recover_line;
479 for(recover_line = client->recover_header; recover_line; recover_line = recover_line->next) {
480 usersession_client_raw(session, recover_line->line);
482 struct IRCChannel *channel;
483 struct IRCChannelMember *member;
485 int prefixlen = strlen((client->network_prefixes ? client->network_prefixes : "@+"));
486 char prefixbuf[prefixlen+1];
487 for(channel = client->channel; channel; channel = channel->next) {
488 sprintf(raw, ":%s!%s@TransparentIRC.session.recover JOIN %s", client->session->nick, client->session->username, channel->name);
489 usersession_client_raw(session, raw);
491 int headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
492 int first_element = 1;
493 for(member = channel->userlist; member; member = member->next) {
494 if(headerlen + strlen(member->nick) + prefixlen + 1 >= LINELEN-2) {
495 usersession_client_raw(session, raw);
496 headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
499 ircclient_build_chanuser_prefix(client, member, prefixbuf, 1);
500 headerlen += sprintf(raw+headerlen, "%s%s%s", (first_element ? "" : " "), prefixbuf, member->nick);
504 usersession_client_raw(session, raw);