added SSL backend for IOMultiplexer
[TransparentIRC.git] / src / IRCClient.c
1 /* IRCClient.c - TransparentIRC 0.1
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
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.
8  * 
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.
13  * 
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/>. 
16  */
17 #include "IRCClient.h"
18 #include "IOHandler.h"
19 #include "UserSession.h"
20 #include "UserClient.h"
21 #include "tools.h"
22 #include "ConfigParser.h"
23
24 static void ircclient_callback(struct IOEvent *event);
25
26 void ircclient_initialize(struct UserSession *session, struct UserLogin *login) {
27     struct IRCClient *client = calloc(1, sizeof(*client));
28     if(!client) {
29         usersession_error(session, "Could not initialize IRC connection.");
30         return;
31     }
32     client->session = session;
33     session->irc = client;
34     
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"));
39     
40     char notification[LINELEN];
41     sprintf(notification, "Connecting to %s (%s%d)", server_address, (server_ssl ? "+" : ""), server_port);
42     usersession_client_notification(session, notification);
43     
44     client->iofd = iohandler_connect(server_address, server_port, server_ssl, bind_address, ircclient_callback);
45     if(!client->iofd) {
46         usersession_error(session, "Could not initialize IRC connection.");
47         return;
48     }
49     client->iofd->data = client;
50 }
51
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);
60         free(recover_line);
61     }
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);
70     free(client);
71 }
72
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;
81 }
82
83 static struct IRCUser ircclient_parse_user(char *from) {
84     struct IRCUser user;
85     char *a = from, *b = from;
86     while(*b != '!') {
87         b++;
88     }
89     user.nick = a;
90     if(!*b) {
91         user.ident = "";
92         user.host = "";
93         return user;
94     }
95     *b = '\0';
96     b++;
97     a = b;
98     while(*b && *b != '@') {
99         b++;
100     }
101     user.ident = a;
102     if(!*b) {
103         user.host = "";
104         return user;
105     }
106     *b = '\0';
107     b++;
108     user.host = b;
109     return user;
110 }
111
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;
116         free(member->nick);
117         free(member);
118     }
119     channel->userlist = NULL;
120 }
121
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
126     }
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;
133     return member;
134 }
135
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;
140     }
141     if(!member) return;
142     if(member->prev)
143         member->prev->next = member->next;
144     else
145         channel->userlist = member->next;
146     if(member->next)
147         member->next->prev = member->prev;
148     free(member->nick);
149     free(member);
150 }
151
152 static void ircclient_raw005_parse(struct IRCClient *client, char **argv, int argc) {
153     int i;
154     char *name;
155     char *value;
156     for(i = 0; i < argc; i++) {
157         name = argv[i];
158         value = strchr(argv[i], '=');
159         if(value) {
160             *value = '\0';
161             value++;
162         }
163         if(!stricmp(name, "PREFIX")) {
164             //parse prefixes
165             if(*value != '(') continue;
166             value++;
167             char *prefixes_char = value;
168             value = strchr(value, ')');
169             if(!value) continue;
170             *value = '\0';
171             value++;
172             if(strlen(prefixes_char) == strlen(value)) {
173                 client->network_prefixes = strdup(value);
174                 client->network_prefixes_char = strdup(prefixes_char);
175             }
176         }
177     }
178 }
179
180 static void ircclient_chanmode_parse(struct IRCClient *client, struct IRCChannel *channel, char *mode, char **argv, int argc) {
181     int add = 1;
182     int arg = 0;
183     int i, j;
184     char *carg;
185     struct IRCChannelMember *member;
186     while(*mode) {
187         if(*mode == '+')
188             add = 1;
189         else if(*mode == '-')
190             add = 0;
191         else {
192             char *prefix_chars = client->network_prefixes_char ? client->network_prefixes_char : "ov";
193             i = 0;
194             while(prefix_chars[i]) {
195                 if(prefix_chars[i] == *mode) {
196                     carg = argv[arg++];
197                     member = ircclient_userlist_add(channel, carg);
198                     if(add)
199                         member->modes |= (1 << i);
200                     else
201                         member->modes &= ~(1 << i);
202                     goto ircclient_chanmode_parse_next;
203                 }
204                 i++;
205             }
206             char *chanmodes = client->network_chanmodes ? client->network_chanmodes : "b,k,l,";
207             i = 0;
208             j = 0;
209             while(chanmodes[i]) {
210                 if(chanmodes[i] == ',') {
211                     j++;
212                     if(j == 2 && !add) break;
213                     if(j == 3) break;
214                 }
215                 if(chanmodes[i] == *mode) {
216                     arg++;
217                     goto ircclient_chanmode_parse_next;
218                 }
219             }
220             //default mode without parameter...
221         }
222         ircclient_chanmode_parse_next:
223         mode++;
224     }
225 }
226
227 static int ircclient_is_channel_prefix(struct IRCClient *client, char prefix) {
228     if(!client->network_chantypes) {
229         return (prefix == '#'); //default
230     }
231     int i = 0;
232     while(client->network_chantypes[i]) {
233         if(prefix == client->network_chantypes[i])
234             return 1;
235         i++;
236     }
237     return 0;
238 }
239
240 static int ircclient_is_chanuser_prefix(struct IRCClient *client, char prefix, int *prefix_id) {
241     char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
242     int i;
243     for(i = 0; prefixes[i]; i++) {
244         if(prefixes[i] == prefix) {
245             *prefix_id = i;
246             return 1;
247         }
248     }
249     return 0;
250 }
251
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 : "@+";
254     int i;
255     int j = 0;
256     for(i = 0; prefixes[i]; i++) {
257         if(member->modes & (1 << i)) {
258             buffer[j++] = prefixes[i];
259             if(highest_only) break;
260         }
261     }
262     buffer[j] = '\0';
263     return j;
264 }
265
266 static void ircclient_recv(struct IRCClient *client, char *line) {
267     struct UserSession *session = client->session;
268     char *argv[MAXNUMPARAMS];
269     char tmp[LINELEN];
270     strcpy(tmp, line);
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]);
275         pass_to_client = 0;
276     }
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)");
282             pass_to_client = 0;
283         }
284     } else {
285         if(!stricmp(argv[1], "001")) {
286             free(session->nick);
287             session->nick = strdup(argv[2]);
288             client->fully_connected = 1;
289         }
290         if(!stricmp(argv[1], "005")) {
291             //parse 005 raw
292             ircclient_raw005_parse(client, argv+3, argc-3);
293         }
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")
302           ) {
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));
308             if(new_line) {
309                 new_line->line = strdup(line);
310                 new_line->next = NULL;
311                 if(recover_line)
312                     recover_line->next = new_line;
313                 else
314                     client->recover_header = new_line;
315             }
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));
322                 if(client->channel)
323                     for(lchannel = client->channel; lchannel->next; lchannel = lchannel->next) {}
324                 channel->name = strdup(argv[2]);
325                 channel->prev = lchannel;
326                 if(lchannel)
327                     lchannel->next = channel;
328                 else
329                     client->channel = channel;
330             } else {
331                 for(channel = client->channel; channel; channel = channel->next) {
332                     if(!stricmp(channel->name, argv[2])) {
333                         ircclient_userlist_add(channel, from.nick);
334                         break;
335                     }
336                 }
337             }
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);
345                         if(channel->prev)
346                             channel->prev->next = channel->next;
347                         else
348                             client->channel = channel->next;
349                         if(channel->next)
350                             channel->next->prev = channel->prev;
351                         free(channel->name);
352                         free(channel);
353                     } else {
354                         ircclient_userlist_del(channel, from.nick);
355                     }
356                     break;
357                 }
358             }
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);
365                         if(channel->prev)
366                             channel->prev->next = channel->next;
367                         else
368                             client->channel = channel->next;
369                         if(channel->next)
370                             channel->next->prev = channel->prev;
371                         free(channel->name);
372                         free(channel);
373                     } else {
374                         ircclient_userlist_del(channel, argv[3]);
375                     }
376                     break;
377                 }
378             }
379         } else if(!stricmp(argv[1], "NICK")) {
380             struct IRCUser from = ircclient_parse_user(argv[0]);
381             if(!stricmp(from.nick, session->nick)) {
382                 free(session->nick);
383                 session->nick = strdup(argv[2]);
384             }
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)) {
390                         free(member->nick);
391                         member->nick = strdup(argv[2]);
392                         break;
393                     }
394                 }
395             }
396         } else if(!stricmp(argv[1], "MODE")) {
397             if(!stricmp(argv[2], session->nick)) {
398                 //user mode
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);
405                     }
406                 }
407             }
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);
416                     }
417                     char *a = argv[5];
418                     char *b, *c;
419                     do {
420                         b = strchr(a, ' ');
421                         if(b) *b = '\0';
422                         if((c = strchr(a, '!'))) {
423                             *c = '\0';
424                         }
425                         int prefix_id;
426                         int member_modes = 0;
427                         while(ircclient_is_chanuser_prefix(client, *a, &prefix_id)) {
428                             member_modes |= (1 << prefix_id);
429                             a++;
430                         }
431                         member = ircclient_userlist_add(channel, a);
432                         member->modes = member_modes;
433                         if(b) a = b+1;
434                     } while(b);
435                     break;
436                 }
437             }
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;
443                 }
444             }
445         }
446     }
447     if(pass_to_client)
448         usersession_client_raw(session, line);
449 }
450
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.");
456             break;
457         case IOEVENT_CONNECTED:
458             ircclient_handshake(client);
459             break;
460         case IOEVENT_RECV:
461             ircclient_recv(client, event->data.recv_str);
462             break;
463         case IOEVENT_CLOSED:
464             usersession_error(client->session, "Disconnected from the IRC Server.");
465             break;
466         default:
467             break;
468     }
469 }
470
471 void ircclient_send(struct IRCClient *client, char *line) {
472     iohandler_printf(client->iofd, "%s", line);
473 }
474
475 void ircclient_recover_session(struct UserSession *session) {
476     struct IRCClient *client = session->irc;
477     //replay header
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);
481     }
482     struct IRCChannel *channel;
483     struct IRCChannelMember *member;
484     char raw[LINELEN];
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);
490         //replay /NAMES
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);
497                 first_element = 1;
498             }
499             ircclient_build_chanuser_prefix(client, member, prefixbuf, 1);
500             headerlen += sprintf(raw+headerlen, "%s%s%s", (first_element ? "" : " "), prefixbuf, member->nick);
501             first_element = 0;
502         }
503         if(!first_element) {
504             usersession_client_raw(session, raw);
505         }
506     }
507 }