$ git clone http://tcclient.ion.nu/tc_client.git
commit ffa4f152ddb7380d4accf273488c6bcd660bde60
Author: Alicia <...>
Date:   Tue Apr 7 06:49:01 2015 +0200

    Version 0.14

diff --git a/ChangeLog b/ChangeLog
index 166407b..aedeef8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+0.14:
+Use iconv to deal with tinychat's ISO-8859-1 instead of the wcs functions.
+irchack: only listen on 127.0.0.1 (localhost)
+Added -u/--user and -p/--pass options to log into tinychat accounts.
 0.13:
 Check the validity of nicknames before switching.
 Roll our own endianness conversion functions since the ones provided by libc are not standardized.
diff --git a/Makefile b/Makefile
index 2c4e561..57e0b58 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.13
+VERSION=0.14
 CFLAGS=-g3 -Wall $(shell curl-config --cflags)
 LIBS=-g3 $(shell curl-config --libs)
 
diff --git a/README b/README
index 897560f..7987c95 100644
--- a/README
+++ b/README
@@ -1,7 +1,6 @@
 Some notes about tc_client in its current state (things that should be fixed):
 *there is no real user interface, to send a message you just type it and it might get cut off by incoming messages, it'll be ugly. There is however the irchack program that lets you use an IRC client.
 *PMs can be sent by /msg <nickname> <message> and replies appear similarly.
-*you can't log in with an account and thus not do mod stuff, I don't know how this works protocol-wise
 
 Some things that will probably never change:
 *tc_client can't view people's webcams or listen to mics
diff --git a/client.c b/client.c
index a1ae3da..7005e21 100644
--- a/client.c
+++ b/client.c
@@ -62,7 +62,7 @@ size_t writehttp(char* ptr, size_t size, size_t nmemb, void* x)
   return size;
 }
 
-char* http_get(char* url)
+char* http_get(const char* url, const char* post)
 {
   CURL* curl=curl_easy_init();
   if(!curl){return 0;}
@@ -70,6 +70,10 @@ char* http_get(char* url)
   struct writebuf writebuf={0, 0};
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writehttp);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writebuf);
+  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+  curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
+  curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla Firefox");
+  if(post){curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);}
   char err[CURL_ERROR_SIZE];
   curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
   if(curl_easy_perform(curl)){curl_easy_cleanup(curl); printf(err); return 0;}
@@ -93,7 +97,7 @@ char* gethost(char *channel, char *password)
   }else{
     sprintf(url, "http://tinychat.com/api/find.room/%s?site=tinychat", channel);
   }
-  char *response=http_get(url);
+  char *response=http_get(url, 0);
   //response contains result='(OK|RES)|PW' (latter means a password is required)
   char* result=strstr(response, "result='");
   if(!result){printf("No result\n"); exit(-1); return 0;}
@@ -116,7 +120,7 @@ char* getkey(char* id, char* channel)
 {
   char url[strlen("http://tinychat.com/api/captcha/check.php?guest%5Fid=&room=tinychat%5E0")+strlen(id)+strlen(channel)];
   sprintf(url, "http://tinychat.com/api/captcha/check.php?guest%%5Fid=%s&room=tinychat%%5E%s", id, channel);
-  char* response=http_get(url);
+  char* response=http_get(url, 0);
   char* key=strstr(response, "\"key\":\"");
 
   if(!key){return 0;}
@@ -134,6 +138,28 @@ char* getkey(char* id, char* channel)
   return key;
 }
 
+char* getmodkey(const char* user, const char* pass, const char* channel)
+{
+  // TODO: if possible, do this in a neater way than digging the key out from an HTML page.
+  if(!user||!pass){return 0;}
+  char post[strlen("form_sent=1&username=&password=&next=http://tinychat.com/0")+strlen(user)+strlen(pass)+strlen(channel)];
+  sprintf(post, "form_sent=1&username=%s&password=%s&next=http://tinychat.com/%s", user, pass, channel);
+  char* response=http_get("http://tinychat.com/login", post);
+  char* key=strstr(response, "autoop: \"");
+  if(key)
+  {
+    key=&key[9];
+    char* end=strchr(key, '"');
+    if(end)
+    {
+      end[0]=0;
+      key=strdup(key);
+    }else{key=0;}
+  }
+  free(response);
+  return key;
+}
+
 char timestampbuf[8];
 char* timestamp()
 {
@@ -154,23 +180,50 @@ char checknick(const char* nick) // Returns zero if the nick is valid, otherwise
   return 0;
 }
 
+void usage(const char* me)
+{
+  printf("Usage: %s [options] <channelname> <nickname> [channelpassword]\n"
+         "Options include:\n"
+         "-h, --help           Show this help text and exit\n"
+         "-u, --user <user>    Username of tinychat account to use.\n"
+         "-p, --pass <pass>    Password of tinychat account to use.\n"
+         ,me);
+}
+
 int main(int argc, char** argv)
 {
-  setlocale(LC_ALL, "");
-  if(argc<3)
-   {
-    printf("Usage: %s <channelname> <nickname> [password]\n", argv[0]);
-    return 1;
+  char* channel=0;
+  char* nickname=0;
+  char* password=0;
+  char* account_user=0;
+  char* account_pass=0;
+  int i;
+  for(i=1; i<argc; ++i)
+  {
+    if(!strcmp(argv[i], "-h")||!strcmp(argv[i], "--help")){usage(argv[0]); return 0;}
+    else if(!strcmp(argv[i], "-u")||!strcmp(argv[i], "--user"))
+    {
+      ++i;
+      account_user=argv[i];
+    }
+    else if(!strcmp(argv[i], "-p")||!strcmp(argv[i], "--pass"))
+    {
+      ++i;
+      account_pass=argv[i];
+    }
+    else if(!channel){channel=argv[i];}
+    else if(!nickname){nickname=argv[i];}
+    else if(!password){password=argv[i];}
   }
+  // Check for required arguments
+  if(!channel||!nickname){usage(argv[0]); return 1;}
   char badchar;
   if((badchar=checknick(argv[2])))
   {
     printf("'%c' is not allowed in nicknames.\n", badchar);
     return 1;
   }
-  char* channel=argv[1];
-  char* nickname=strdup(argv[2]);
-  char* password=(argc>3?argv[3]:0);
+  setlocale(LC_ALL, "");
   char* server=gethost(channel, password);
   struct addrinfo* res;
   // Separate IP/domain and port
@@ -202,7 +255,9 @@ int main(int argc, char** argv)
   b_read(sock, handshake, 1536); // Read our junk back, we don't bother checking that it's the same
   printf("Handshake complete\n");
   struct rtmp rtmp={0,0,0};
-  // Handshake complete, send connect request
+  // Handshake complete, log in (if user account is specified)
+  char* modkey=getmodkey(account_user, account_pass, channel);
+  // Send connect request
   struct rtmp amf;
   amfinit(&amf, 3);
   amfstring(&amf, "connect");
@@ -246,11 +301,12 @@ int main(int argc, char** argv)
     amfnum(&amf, 0);
   amfobjend(&amf);
   amfstring(&amf, channel);
-  amfstring(&amf, "none");
+  amfstring(&amf, modkey?modkey:"none");
   amfstring(&amf, "default"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to, but "default" seems to work fine too.
   amfstring(&amf, "tinychat");
-  amfstring(&amf, "");
+  amfstring(&amf, modkey?account_user:"");
   amfsend(&amf, sock);
+  free(modkey);
 
   struct pollfd pfd[2];
   pfd[0].fd=0;
@@ -327,10 +383,7 @@ int main(int argc, char** argv)
           }
         }
       }
-      len=mbstowcs(0, (char*)buf, 0);
-      wchar_t wcsbuf[len+1];
-      mbstowcs(wcsbuf, (char*)buf, len+1);
-      char* msg=tonumlist(wcsbuf);
+      char* msg=tonumlist((char*)buf, len);
       amfinit(&amf, 3);
       amfstring(&amf, "privmsg");
       amfnum(&amf, 0);
@@ -386,13 +439,12 @@ int main(int argc, char** argv)
       // Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
       else if(amfin->itemcount>5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "privmsg") && amfin->items[3].type==AMF_STRING && amfin->items[4].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
       {
-        wchar_t* msg=fromnumlist(amfin->items[3].string.string);
+        size_t len;
+        char* msg=fromnumlist(amfin->items[3].string.string, &len);
         const char* color=(showcolor?resolvecolor(amfin->items[4].string.string):"0");
-#ifndef __ANDROID__
-        printf("%s \x1b[%sm%s: %ls\x1b[0m\n", timestamp(), color, amfin->items[5].string.string, msg);
-#else // Wide characters are broken on android
-        printf("%s \x1b[%sm%s: %s\x1b[0m\n", timestamp(), color, amfin->items[5].string.string, msg);
-#endif
+        printf("%s \x1b[%sm%s: ", timestamp(), color, amfin->items[5].string.string);
+        fwrite(msg, len, 1, stdout);
+        printf("\x1b[0m\n");
         free(msg);
         fflush(stdout);
       }
@@ -491,7 +543,7 @@ int main(int argc, char** argv)
       // nickinuse, the nick we wanted to change to is already taken
       else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING &&  amf_comparestrings_c(&amfin->items[0].string, "nickinuse"))
       {
-        printf("Nick %s is already in use.\n", nickname);
+        printf("Nick is already in use.\n");
         fflush(stdout);
       }
       // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
diff --git a/irchack.c b/irchack.c
index 67a45dc..e376f0b 100644
--- a/irchack.c
+++ b/irchack.c
@@ -1,6 +1,6 @@
 /*
     irchack, a simple application to reuse IRC clients as user interfaces for tc_client
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
     Copyright (C) 2015  Jade Lea
 
     This program is free software: you can redistribute it and/or modify
@@ -105,7 +105,7 @@ int main(int argc, char** argv)
   struct sockaddr_in addr;
   memset(&addr, 0, sizeof(addr));
   addr.sin_family=AF_INET;
-  addr.sin_addr.s_addr=0;
+  addr.sin_addr.s_addr=htonl(0x7f000001); // 127.0.0.1
   addr.sin_port=htons(port);
   int lsock=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
   if(bind(lsock, (struct sockaddr*)&addr, sizeof(addr))){perror("bind"); return 1;}
diff --git a/numlist.c b/numlist.c
index b1806a2..5051d26 100644
--- a/numlist.c
+++ b/numlist.c
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -17,22 +17,21 @@
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <wchar.h>
-#include <locale.h>
+#include <iconv.h>
 #include "numlist.h"
 
 // Functions for converting to/from the comma-separated decimal character code format that tinychat uses for chat messages, e.g. "97,98,99" = "abc"
 
-wchar_t* fromnumlist(char* in)
+char* fromnumlist(char* in, size_t* outlen)
 {
-  int len=1;
+  size_t len=1;
   char* x=in;
   while((x=strchr(x, ',')))
   {
     ++len;
     x=&x[1];
   }
-  unsigned char string[len+1];
+  char string[len+1];
   int i;
   for(i=0; i<len; ++i)
   {
@@ -41,22 +40,30 @@ wchar_t* fromnumlist(char* in)
     in=&in[1];
   }
   string[len]=0;
-  wchar_t* wstring=malloc(sizeof(wchar_t)*(len+1));
-  setlocale(LC_ALL, "en_US.ISO-8859-1");
-  mbstowcs(wstring, (char*)string, len);
-  wstring[len]=0;
-  setlocale(LC_ALL, "");
-  return wstring;
+
+  iconv_t cd=iconv_open("", "iso-8859-1");
+  char* outbuf=malloc(len*4);
+  char* i_out=outbuf;
+  char* i_in=string;
+  size_t remaining=len*4;
+  *outlen=remaining;
+  while(outlen>0 && len>0 && iconv(cd, &i_in, &len, &i_out, &remaining)>0);
+  i_out[0]=0; // null-terminates 'outbuf'
+  iconv_close(cd);
+  *outlen-=remaining;
+  return outbuf;
 }
 
-char* tonumlist(const wchar_t* in_x)
+char* tonumlist(char* i_in, size_t len)
 {
-  int len=wcslen(in_x);
+  iconv_t cd=iconv_open("iso-8859-1", "");
   char in[len+1];
-  setlocale(LC_ALL, "en_US.ISO-8859-1");
-  wcstombs(in, in_x, len);
-  in[len]=0;
-  setlocale(LC_ALL, "");
+  char* i_out=in;
+  size_t outlen=len;
+  while(outlen>0 && len>0 && iconv(cd, &i_in, &len, &i_out, &outlen)>0);
+  i_out[0]=0; // null-terminates the 'in' buffer
+  iconv_close(cd);
+
   char* out=malloc(strlen(in)*strlen("255,"));
   out[0]=0;
   char* x=out;
diff --git a/numlist.h b/numlist.h
index 963daff..7144bef 100644
--- a/numlist.h
+++ b/numlist.h
@@ -1,6 +1,6 @@
 /*
     tc_client, a simple non-flash client for tinychat(.com)
-    Copyright (C) 2014  alicia@ion.nu
+    Copyright (C) 2014-2015  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -14,12 +14,5 @@
     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
-#ifdef __ANDROID__ // Compatibility hacks for android
-  #define wchar_t char
-  #define mbstowcs(dst,src,len) ((dst)?(int)strncpy(dst,src,len):strlen(src))
-  #define wcstombs strncpy
-  #define wcslen strlen
-#endif
-
-extern wchar_t* fromnumlist(char* in);
-extern char* tonumlist(const wchar_t* in);
+extern char* fromnumlist(char* in, size_t* outlen);
+extern char* tonumlist(char* in_x, size_t len);