added SSL backend for IOMultiplexer
[TransparentIRC.git] / src / IRCClient.c
index eb91bafdbf684f3e6671269bb03c2dcbdfd6c61f..7720602acac787f1658762dea9b2b116cb702017 100644 (file)
@@ -59,6 +59,14 @@ void ircclient_close(struct IRCClient *client) {
         free(recover_line->line);
         free(recover_line);
     }
+    if(client->network_prefixes)
+        free(client->network_prefixes);
+    if(client->network_prefixes_char)
+        free(client->network_prefixes_char);
+    if(client->network_chanmodes)
+        free(client->network_chanmodes);
+    if(client->network_chantypes)
+        free(client->network_chantypes);
     free(client);
 }
 
@@ -72,6 +80,189 @@ static void ircclient_handshake(struct IRCClient *client) {
     session->realname = NULL;
 }
 
+static struct IRCUser ircclient_parse_user(char *from) {
+    struct IRCUser user;
+    char *a = from, *b = from;
+    while(*b != '!') {
+        b++;
+    }
+    user.nick = a;
+    if(!*b) {
+        user.ident = "";
+        user.host = "";
+        return user;
+    }
+    *b = '\0';
+    b++;
+    a = b;
+    while(*b && *b != '@') {
+        b++;
+    }
+    user.ident = a;
+    if(!*b) {
+        user.host = "";
+        return user;
+    }
+    *b = '\0';
+    b++;
+    user.host = b;
+    return user;
+}
+
+static void ircclient_userlist_clear(struct IRCChannel *channel) {
+    struct IRCChannelMember *member, *next_member;
+    for(member = channel->userlist; member; member = next_member) {
+        next_member = member->next;
+        free(member->nick);
+        free(member);
+    }
+    channel->userlist = NULL;
+}
+
+static struct IRCChannelMember *ircclient_userlist_add(struct IRCChannel *channel, char *nick) {
+    struct IRCChannelMember *member;
+    for(member = channel->userlist; member; member = member->next) {
+        if(!stricmp(member->nick, nick)) return member; //prevent duplicates
+    }
+    member = calloc(1, sizeof(*member));
+    member->nick = strdup(nick);
+    member->next = channel->userlist;
+    if(channel->userlist)
+        channel->userlist->prev = member;
+    channel->userlist = member;
+    return member;
+}
+
+static void ircclient_userlist_del(struct IRCChannel *channel, char *nick) {
+    struct IRCChannelMember *member;
+    for(member = channel->userlist; member; member = member->next) {
+        if(!stricmp(member->nick, nick)) break;
+    }
+    if(!member) return;
+    if(member->prev)
+        member->prev->next = member->next;
+    else
+        channel->userlist = member->next;
+    if(member->next)
+        member->next->prev = member->prev;
+    free(member->nick);
+    free(member);
+}
+
+static void ircclient_raw005_parse(struct IRCClient *client, char **argv, int argc) {
+    int i;
+    char *name;
+    char *value;
+    for(i = 0; i < argc; i++) {
+        name = argv[i];
+        value = strchr(argv[i], '=');
+        if(value) {
+            *value = '\0';
+            value++;
+        }
+        if(!stricmp(name, "PREFIX")) {
+            //parse prefixes
+            if(*value != '(') continue;
+            value++;
+            char *prefixes_char = value;
+            value = strchr(value, ')');
+            if(!value) continue;
+            *value = '\0';
+            value++;
+            if(strlen(prefixes_char) == strlen(value)) {
+                client->network_prefixes = strdup(value);
+                client->network_prefixes_char = strdup(prefixes_char);
+            }
+        }
+    }
+}
+
+static void ircclient_chanmode_parse(struct IRCClient *client, struct IRCChannel *channel, char *mode, char **argv, int argc) {
+    int add = 1;
+    int arg = 0;
+    int i, j;
+    char *carg;
+    struct IRCChannelMember *member;
+    while(*mode) {
+        if(*mode == '+')
+            add = 1;
+        else if(*mode == '-')
+            add = 0;
+        else {
+            char *prefix_chars = client->network_prefixes_char ? client->network_prefixes_char : "ov";
+            i = 0;
+            while(prefix_chars[i]) {
+                if(prefix_chars[i] == *mode) {
+                    carg = argv[arg++];
+                    member = ircclient_userlist_add(channel, carg);
+                    if(add)
+                        member->modes |= (1 << i);
+                    else
+                        member->modes &= ~(1 << i);
+                    goto ircclient_chanmode_parse_next;
+                }
+                i++;
+            }
+            char *chanmodes = client->network_chanmodes ? client->network_chanmodes : "b,k,l,";
+            i = 0;
+            j = 0;
+            while(chanmodes[i]) {
+                if(chanmodes[i] == ',') {
+                    j++;
+                    if(j == 2 && !add) break;
+                    if(j == 3) break;
+                }
+                if(chanmodes[i] == *mode) {
+                    arg++;
+                    goto ircclient_chanmode_parse_next;
+                }
+            }
+            //default mode without parameter...
+        }
+        ircclient_chanmode_parse_next:
+        mode++;
+    }
+}
+
+static int ircclient_is_channel_prefix(struct IRCClient *client, char prefix) {
+    if(!client->network_chantypes) {
+        return (prefix == '#'); //default
+    }
+    int i = 0;
+    while(client->network_chantypes[i]) {
+        if(prefix == client->network_chantypes[i])
+            return 1;
+        i++;
+    }
+    return 0;
+}
+
+static int ircclient_is_chanuser_prefix(struct IRCClient *client, char prefix, int *prefix_id) {
+    char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
+    int i;
+    for(i = 0; prefixes[i]; i++) {
+        if(prefixes[i] == prefix) {
+            *prefix_id = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int ircclient_build_chanuser_prefix(struct IRCClient *client, struct IRCChannelMember *member, char *buffer, int highest_only) {
+    char *prefixes = client->network_prefixes ? client->network_prefixes : "@+";
+    int i;
+    int j = 0;
+    for(i = 0; prefixes[i]; i++) {
+        if(member->modes & (1 << i)) {
+            buffer[j++] = prefixes[i];
+            if(highest_only) break;
+        }
+    }
+    buffer[j] = '\0';
+    return j;
+}
+
 static void ircclient_recv(struct IRCClient *client, char *line) {
     struct UserSession *session = client->session;
     char *argv[MAXNUMPARAMS];
@@ -91,6 +282,15 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
             pass_to_client = 0;
         }
     } else {
+        if(!stricmp(argv[1], "001")) {
+            free(session->nick);
+            session->nick = strdup(argv[2]);
+            client->fully_connected = 1;
+        }
+        if(!stricmp(argv[1], "005")) {
+            //parse 005 raw
+            ircclient_raw005_parse(client, argv+3, argc-3);
+        }
         if(!stricmp(argv[1], "001") ||
             !stricmp(argv[1], "002") ||
             !stricmp(argv[1], "003") ||
@@ -113,6 +313,135 @@ static void ircclient_recv(struct IRCClient *client, char *line) {
                 else
                     client->recover_header = new_line;
             }
+        } else if(!stricmp(argv[1], "JOIN")) {
+            struct IRCUser from = ircclient_parse_user(argv[0]);
+            struct IRCChannel *channel;
+            if(!stricmp(from.nick, session->nick)) {
+                struct IRCChannel *lchannel = NULL;
+                channel = calloc(1, sizeof(*channel));
+                if(client->channel)
+                    for(lchannel = client->channel; lchannel->next; lchannel = lchannel->next) {}
+                channel->name = strdup(argv[2]);
+                channel->prev = lchannel;
+                if(lchannel)
+                    lchannel->next = channel;
+                else
+                    client->channel = channel;
+            } else {
+                for(channel = client->channel; channel; channel = channel->next) {
+                    if(!stricmp(channel->name, argv[2])) {
+                        ircclient_userlist_add(channel, from.nick);
+                        break;
+                    }
+                }
+            }
+        } else if(!stricmp(argv[1], "PART")) {
+            struct IRCUser from = ircclient_parse_user(argv[0]);
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[2])) {
+                    if(!stricmp(from.nick, session->nick)) {
+                        ircclient_userlist_clear(channel);
+                        if(channel->prev)
+                            channel->prev->next = channel->next;
+                        else
+                            client->channel = channel->next;
+                        if(channel->next)
+                            channel->next->prev = channel->prev;
+                        free(channel->name);
+                        free(channel);
+                    } else {
+                        ircclient_userlist_del(channel, from.nick);
+                    }
+                    break;
+                }
+            }
+        } else if(!stricmp(argv[1], "KICK")) {
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[2])) {
+                    if(!stricmp(argv[3], session->nick)) {
+                        ircclient_userlist_clear(channel);
+                        if(channel->prev)
+                            channel->prev->next = channel->next;
+                        else
+                            client->channel = channel->next;
+                        if(channel->next)
+                            channel->next->prev = channel->prev;
+                        free(channel->name);
+                        free(channel);
+                    } else {
+                        ircclient_userlist_del(channel, argv[3]);
+                    }
+                    break;
+                }
+            }
+        } else if(!stricmp(argv[1], "NICK")) {
+            struct IRCUser from = ircclient_parse_user(argv[0]);
+            if(!stricmp(from.nick, session->nick)) {
+                free(session->nick);
+                session->nick = strdup(argv[2]);
+            }
+            struct IRCChannel *channel;
+            struct IRCChannelMember *member;
+            for(channel = client->channel; channel; channel = channel->next) {
+                for(member = channel->userlist; member; member = member->next) {
+                    if(!stricmp(member->nick, from.nick)) {
+                        free(member->nick);
+                        member->nick = strdup(argv[2]);
+                        break;
+                    }
+                }
+            }
+        } else if(!stricmp(argv[1], "MODE")) {
+            if(!stricmp(argv[2], session->nick)) {
+                //user mode
+            } else if(ircclient_is_channel_prefix(client, argv[2][0])) {
+                struct IRCChannel *channel;
+                struct IRCChannelMember *member;
+                for(channel = client->channel; channel; channel = channel->next) {
+                    for(member = channel->userlist; member; member = member->next) {
+                        ircclient_chanmode_parse(client, channel, argv[3], argv+4, argc-4);
+                    }
+                }
+            }
+        } else if(!stricmp(argv[1], "353")) {
+            struct IRCChannel *channel;
+            struct IRCChannelMember *member;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[4])) {
+                    if(!channel->synchronizing_userlist) {
+                        channel->synchronizing_userlist = 1;
+                        ircclient_userlist_clear(channel);
+                    }
+                    char *a = argv[5];
+                    char *b, *c;
+                    do {
+                        b = strchr(a, ' ');
+                        if(b) *b = '\0';
+                        if((c = strchr(a, '!'))) {
+                            *c = '\0';
+                        }
+                        int prefix_id;
+                        int member_modes = 0;
+                        while(ircclient_is_chanuser_prefix(client, *a, &prefix_id)) {
+                            member_modes |= (1 << prefix_id);
+                            a++;
+                        }
+                        member = ircclient_userlist_add(channel, a);
+                        member->modes = member_modes;
+                        if(b) a = b+1;
+                    } while(b);
+                    break;
+                }
+            }
+        } else if(!stricmp(argv[1], "366")) {
+            struct IRCChannel *channel;
+            for(channel = client->channel; channel; channel = channel->next) {
+                if(!stricmp(channel->name, argv[3])) {
+                    channel->synchronizing_userlist = 0;
+                }
+            }
         }
     }
     if(pass_to_client)
@@ -150,5 +479,29 @@ void ircclient_recover_session(struct UserSession *session) {
     for(recover_line = client->recover_header; recover_line; recover_line = recover_line->next) {
         usersession_client_raw(session, recover_line->line);
     }
-    
+    struct IRCChannel *channel;
+    struct IRCChannelMember *member;
+    char raw[LINELEN];
+    int prefixlen = strlen((client->network_prefixes ? client->network_prefixes : "@+"));
+    char prefixbuf[prefixlen+1];
+    for(channel = client->channel; channel; channel = channel->next) {
+        sprintf(raw, ":%s!%s@TransparentIRC.session.recover JOIN %s", client->session->nick, client->session->username, channel->name);
+        usersession_client_raw(session, raw);
+        //replay /NAMES
+        int headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
+        int first_element = 1;
+        for(member = channel->userlist; member; member = member->next) {
+            if(headerlen + strlen(member->nick) + prefixlen + 1 >= LINELEN-2) {
+                usersession_client_raw(session, raw);
+                headerlen = sprintf(raw, ":TransparentIRC.session.recover 353 %s = %s :", client->session->nick, channel->name);
+                first_element = 1;
+            }
+            ircclient_build_chanuser_prefix(client, member, prefixbuf, 1);
+            headerlen += sprintf(raw+headerlen, "%s%s%s", (first_element ? "" : " "), prefixbuf, member->nick);
+            first_element = 0;
+        }
+        if(!first_element) {
+            usersession_client_raw(session, raw);
+        }
+    }
 }