$ git clone http://tcclient.ion.nu/tc_client.git
commit 351b4f77fab643c8b2823540b958d6b596bc5c11
Author: Alicia <...>
Date: Tue Apr 7 06:49:00 2015 +0200
Version 0.3
diff --git a/ChangeLog b/ChangeLog
index acf024e..79cf358 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,2 +1,6 @@
+0.3:
+Handle 'joins', 'join', 'nick' and 'quit' messages from the server (contributed by Jade)
+Moved the AMF message writing functions into a separate source file.
+Added irchack.c, a simple application that acts as an IRC server mirroring a tinychat channel using tc_client.
0.2:
Added support for non-ascii characters (sent as ISO-8859-1 to/from the server)
diff --git a/Makefile b/Makefile
index c2adc8d..cc34c31 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,12 @@
-VERSION=0.2
+VERSION=0.3
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
-tc_client: client.o amfparser.o rtmp.o numlist.o
+tc_client: client.o amfparser.o rtmp.o numlist.o amfwriter.o
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
clean:
- rm -f client.o amfparser.o rtmp.o numlist.o tc_client
+ rm -f client.o amfparser.o rtmp.o numlist.o amfwriter.o tc_client
tarball:
- tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfparser.h rtmp.h numlist.h LICENSE README ChangeLog
+ tar -cJf tc_client-$(VERSION).tar.xz --transform='s|^|tc_client-$(VERSION)/|' Makefile client.c amfparser.c rtmp.c numlist.c amfwriter.c amfparser.h rtmp.h numlist.h amfwriter.h LICENSE README ChangeLog irchack.c
diff --git a/README b/README
index d2293ce..9c4cb28 100644
--- a/README
+++ b/README
@@ -1,11 +1,17 @@
-This is a pre-release of tc_client, it's still not ready for a release beyond sharing it with a small group, most significantly because the channel is hardcoded.
-
-Some other notes about tc_client in its current state (things that should be fixed):
+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.
-*PMs can be sent by /msg <nickname> <message> and replies appear similarly
-*you can't log in with an account and thus not to mod stuff, I don't know how this works protocol-wise
+*PMs can be sent by /msg <nickname> <message> and replies appear similarly. Note however that PMs sent this way are currently sent to everyone in the channel, but the flash client ignores them.
+*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
*tc_client can't stream/broadcast your webcam/mic
*tc_client probably won't ever play youtube videos (although you can see when one is launched and get the video ID because it is sent as a message and can then open it yourself)
+
+Current commands sent by the TC servers that tc_client doesn't know how to handle:
+notice
+topic
+joinsdone
+avons (list of people currently on cam)
+pros
+oper
diff --git a/amfwriter.c b/amfwriter.c
index 3fc07dc..38845f3 100644
--- a/amfwriter.c
+++ b/amfwriter.c
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014-2015 alicia@ion.nu
+ Copyright (C) 2014 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
@@ -18,89 +18,119 @@
#include <string.h>
#include <stdint.h>
#include <unistd.h>
-#include "endian.h"
+#include <arpa/inet.h>
#include "rtmp.h"
#include "amfwriter.h"
-void amfinit(struct rtmp* msg, unsigned int chunkid)
+extern unsigned int flip(unsigned int bits, int bytecount);
+
+void amfinit(struct amfmsg* msg)
+{
+ msg->len=0;
+ msg->buf=malloc(sizeof(struct rtmph));
+}
+
+void amfsend(struct amfmsg* msg, int sock)
{
- msg->type=RTMP_AMF0;
- msg->chunkid=chunkid;
- msg->length=0;
- msg->msgid=0;
+ struct rtmph* head=(struct rtmph*)msg->buf;
+ head->streamid=3;
+ head->fmt=0;
+ head->timestamp=0; // Time is irrelevant
+ head->length=flip(msg->len, 3);
+ head->type=20;
+ head->msgid=0;
+ // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
+// printf("Writing %i-byte message\n", msg->len);
+ write(sock, msg->buf, sizeof(struct rtmph));
+ unsigned char* pos=msg->buf+sizeof(struct rtmph);
+ while(msg->len>0)
+ {
+ int w;
+ if(msg->len>128)
+ {
+ w=write(sock, pos, 128);
+ w+=write(sock, "\xc3", 1);
+ msg->len-=128;
+ }else{
+ w=write(sock, pos, msg->len);
+ msg->len=0;
+ }
+// printf("Wrote %i bytes\n", w);
+ pos+=128;
+ }
+ free(msg->buf);
msg->buf=0;
}
-void amfnum(struct rtmp* msg, double v)
+void amfnum(struct amfmsg* msg, double v)
{
- unsigned long long* endian=(void*)&v;
- *endian=be64(*endian);
- int offset=msg->length;
- msg->length+=1+sizeof(double);
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=1+sizeof(double);
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* type=msg->buf+offset;
type[0]='\x00';
- memcpy(msg->buf+offset+1, &v, sizeof(v));
+ double* value=(double*)(msg->buf+offset+1);
+ *value=v;
}
-void amfbool(struct rtmp* msg, char v)
+void amfbool(struct amfmsg* msg, char v)
{
- int offset=msg->length;
- msg->length+=2;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=2;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* x=msg->buf+offset;
x[0]='\x01';
x[1]=!!v;
}
-void amfstring(struct rtmp* msg, const char* string)
+void amfstring(struct amfmsg* msg, char* string)
{
int len=strlen(string);
- int offset=msg->length;
- msg->length+=3+len;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=3+len;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* type=msg->buf+offset;
type[0]='\x02';
uint16_t* strlength=(uint16_t*)(msg->buf+offset+1);
- *strlength=be16(len);
+ *strlength=htons(len);
memcpy(msg->buf+offset+3, string, len);
}
-void amfobjstart(struct rtmp* msg)
+void amfobjstart(struct amfmsg* msg)
{
- int offset=msg->length;
- msg->length+=1;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=1;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* type=msg->buf+offset;
type[0]='\x03';
}
-void amfobjitem(struct rtmp* msg, char* name)
+void amfobjitem(struct amfmsg* msg, char* name)
{
int len=strlen(name);
- int offset=msg->length;
- msg->length+=2+len;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=2+len;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
uint16_t* strlength=(uint16_t*)(msg->buf+offset);
- *strlength=be16(len);
+ *strlength=htons(len);
memcpy(msg->buf+offset+2, name, len);
}
-void amfobjend(struct rtmp* msg)
+void amfobjend(struct amfmsg* msg)
{
amfobjitem(msg, "");
- int offset=msg->length;
- msg->length+=1;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=1;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* type=msg->buf+offset;
type[0]='\x09';
}
-void amfnull(struct rtmp* msg)
+void amfnull(struct amfmsg* msg)
{
- int offset=msg->length;
- msg->length+=1;
- msg->buf=realloc(msg->buf, msg->length);
+ int offset=sizeof(struct rtmph)+msg->len;
+ msg->len+=1;
+ msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
unsigned char* type=msg->buf+offset;
type[0]='\x05';
}
diff --git a/amfwriter.h b/amfwriter.h
index 62f735e..329b4ae 100644
--- a/amfwriter.h
+++ b/amfwriter.h
@@ -1,6 +1,6 @@
/*
tc_client, a simple non-flash client for tinychat(.com)
- Copyright (C) 2014-2015 alicia@ion.nu
+ Copyright (C) 2014 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
@@ -15,13 +15,18 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-extern void amfinit(struct rtmp* msg, unsigned int streamid);
-extern void amfnum(struct rtmp* msg, double v);
-extern void amfbool(struct rtmp* msg, char v);
-extern void amfstring(struct rtmp* msg, const char* string);
-extern void amfobjstart(struct rtmp* msg);
-extern void amfobjitem(struct rtmp* msg, char* name);
-extern void amfobjend(struct rtmp* msg);
-extern void amfnull(struct rtmp* msg);
+struct amfmsg
+{
+ unsigned int len;
+ unsigned char* buf;
+};
-#define amfsend(rtmp,sock) rtmp_send(sock,rtmp); free((rtmp)->buf); (rtmp)->buf=0
+extern void amfinit(struct amfmsg* msg);
+extern void amfsend(struct amfmsg* msg, int sock);
+extern void amfnum(struct amfmsg* msg, double v);
+extern void amfbool(struct amfmsg* msg, char v);
+extern void amfstring(struct amfmsg* msg, char* string);
+extern void amfobjstart(struct amfmsg* msg);
+extern void amfobjitem(struct amfmsg* msg, char* name);
+extern void amfobjend(struct amfmsg* msg);
+extern void amfnull(struct amfmsg* msg);
diff --git a/client.c b/client.c
index 0f95878..8497b16 100644
--- a/client.c
+++ b/client.c
@@ -27,12 +27,7 @@
#include "rtmp.h"
#include "amfparser.h"
#include "numlist.h"
-
-struct amfmsg
-{
- unsigned int len;
- unsigned char* buf;
-};
+#include "amfwriter.h"
unsigned int flip(unsigned int bits, int bytecount)
{
@@ -47,117 +42,6 @@ unsigned int flip(unsigned int bits, int bytecount)
return ret;
}
-void amfinit(struct amfmsg* msg)
-{
- msg->len=0;
- msg->buf=malloc(sizeof(struct rtmph));
-}
-
-void amfsend(struct amfmsg* msg, int sock)
-{
- struct rtmph* head=(struct rtmph*)msg->buf;
- head->streamid=3;
- head->fmt=0;
- head->timestamp=0; // Time is irrelevant
- head->length=flip(msg->len, 3);
- head->type=20;
- head->msgid=0;
- // Send 128 bytes at a time separated by 0xc3 (because apparently that's something RTMP requires)
-// printf("Writing %i-byte message\n", msg->len);
- write(sock, msg->buf, sizeof(struct rtmph));
- unsigned char* pos=msg->buf+sizeof(struct rtmph);
- while(msg->len>0)
- {
- int w;
- if(msg->len>128)
- {
- w=write(sock, pos, 128);
- w+=write(sock, "\xc3", 1);
- msg->len-=128;
- }else{
- w=write(sock, pos, msg->len);
- msg->len=0;
- }
-// printf("Wrote %i bytes\n", w);
- pos+=128;
- }
- free(msg->buf);
- msg->buf=0;
-}
-
-void amfnum(struct amfmsg* msg, double v)
-{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1+sizeof(double);
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* type=msg->buf+offset;
- type[0]='\x00';
- double* value=(double*)(msg->buf+offset+1);
- *value=v;
-}
-
-void amfbool(struct amfmsg* msg, char v)
-{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=2;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* x=msg->buf+offset;
- x[0]='\x01';
- x[1]=!!v;
-}
-
-void amfstring(struct amfmsg* msg, char* string)
-{
- int len=strlen(string);
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=3+len;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* type=msg->buf+offset;
- type[0]='\x02';
- uint16_t* strlength=(uint16_t*)(msg->buf+offset+1);
- *strlength=htons(len);
- memcpy(msg->buf+offset+3, string, len);
-}
-
-void amfobjstart(struct amfmsg* msg)
-{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* type=msg->buf+offset;
- type[0]='\x03';
-}
-
-void amfobjitem(struct amfmsg* msg, char* name)
-{
- int len=strlen(name);
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=2+len;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- uint16_t* strlength=(uint16_t*)(msg->buf+offset);
- *strlength=htons(len);
- memcpy(msg->buf+offset+2, name, len);
-}
-
-void amfobjend(struct amfmsg* msg)
-{
- amfobjitem(msg, "");
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* type=msg->buf+offset;
- type[0]='\x09';
-}
-
-void amfnull(struct amfmsg* msg)
-{
- int offset=sizeof(struct rtmph)+msg->len;
- msg->len+=1;
- msg->buf=realloc(msg->buf, sizeof(struct rtmph)+msg->len);
- unsigned char* type=msg->buf+offset;
- type[0]='\x05';
-}
-
void b_read(int sock, void* buf, size_t len)
{
while(len>0)
@@ -254,7 +138,7 @@ int main(int argc, char** argv)
setlocale(LC_ALL, "");
if(argc<3)
{
- printf("Usage: %s <roomname> <nickname> [password]\n", argv[0]);
+ printf("Usage: %s <channelname> <nickname> [password]\n", argv[0]);
return 1;
}
char* channel=argv[1];
@@ -299,10 +183,10 @@ int main(int argc, char** argv)
amfstring(&amf, "tinyconf");
amfobjitem(&amf, "flashVer");
- amfstring(&amf, "LNX 11,2,202,424");
+ amfstring(&amf, "no flash");
amfobjitem(&amf, "swfUrl");
- amfstring(&amf, "http://tinychat.com/embed/Tinychat-11.1-1.0.0.0602.swf?version=1.0.0.0602/[[DYNAMIC]]/8");
+ amfstring(&amf, "no flash");
amfobjitem(&amf, "tcUrl");
char str[strlen("rtmp://:/tinyconf0")+strlen(server)+strlen(port)];
@@ -331,16 +215,16 @@ int main(int argc, char** argv)
amfobjitem(&amf, "objectEncoding");
amfnum(&amf, 0);
- amfobjend(&amf);
- amfstring(&amf, channel);
- amfstring(&amf, "none");
- amfstring(&amf, "show"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to
- amfstring(&amf, "tinychat");
- amfstring(&amf, "");
- amfsend(&amf, sock);
-
- unsigned char buf[2048];
- int len=read(sock, buf, 2048);
+ amfobjend(&amf);
+ amfstring(&amf, channel);
+ amfstring(&amf, "none");
+ amfstring(&amf, "show"); // This item is called roomtype in the same HTTP response that gives us the server (IP+port) to connect to
+ amfstring(&amf, "tinychat");
+ amfstring(&amf, "");
+ amfsend(&amf, sock);
+
+ unsigned char buf[2048];
+ int len=read(sock, buf, 2048);
// printf("Received %i byte response\n", len);
/* Debugging
int f=open("output", O_WRONLY|O_CREAT|O_TRUNC, 0644);
@@ -348,56 +232,38 @@ int main(int argc, char** argv)
close(f);
*/
-/* As they appear in the flash client:
- char* colors[]={
- "#1965b6,en",
- "#32a5d9,en",
- "#7db257,en",
- "#a78901,en",
- "#9d5bb5,en",
- "#5c1a7a,en",
- "#c53332,en",
- "#821615,en",
- "#a08f23,en",
- "#487d21,en",
- "#c356a3,en",
- "#1d82eb,en",
- "#919104,en",
- "#00a990,en",
- "#b9807f,en",
- "#7bb224,en"
- };
+/* Disabled for now
+ char* colors[]={ // Sorted like a rainbow
+ "#821615,en",
+ "#c53332,en",
+ "#a08f23,en",
+ "#a78901,en",
+ "#919104,en",
+ "#7bb224,en",
+ "#7db257,en",
+ "#487d21,en",
+ "#00a990,en",
+ "#32a5d9,en",
+ "#1d82eb,en",
+ "#1965b6,en",
+ "#5c1a7a,en",
+ "#9d5bb5,en",
+ "#c356a3,en",
+ "#b9807f,en"
+ };
+ unsigned int currentcolor=0;
*/
- char* colors[]={ // Sorted like a rainbow
- "#821615,en",
- "#c53332,en",
- "#a08f23,en",
- "#a78901,en",
- "#919104,en",
- "#7bb224,en",
- "#7db257,en",
- "#487d21,en",
- "#00a990,en",
- "#32a5d9,en",
- "#1d82eb,en",
- "#1965b6,en",
- "#5c1a7a,en",
- "#9d5bb5,en",
- "#c356a3,en",
- "#b9807f,en"
- };
- unsigned int currentcolor=0;
// int outnum=2; (Debugging, number for output filenames)
- struct pollfd pfd[2];
- pfd[0].fd=0;
- pfd[0].events=POLLIN;
- pfd[0].revents=0;
- pfd[1].fd=sock;
- pfd[1].events=POLLIN;
- pfd[1].revents=0;
- while(1)
- {
+ struct pollfd pfd[2];
+ pfd[0].fd=0;
+ pfd[0].events=POLLIN;
+ pfd[0].revents=0;
+ pfd[1].fd=sock;
+ pfd[1].events=POLLIN;
+ pfd[1].revents=0;
+ while(1)
+ {
// Poll for input, very crude chat UI
poll(pfd, 2, -1);
if(pfd[0].revents) // Got input, send a privmsg command
@@ -415,10 +281,11 @@ int main(int argc, char** argv)
amfnum(&amf, 0);
amfnull(&amf);
amfstring(&amf, msg);
- // The alternating color thing is an unnecessary but fun feature
- amfstring(&amf, colors[currentcolor%16]);
+ // amfstring(&amf, colors[currentcolor%16]); // Alternating colors, fun but annoying in the long run
+ amfstring(&amf, "#000000,en");
+// TODO: for PMs, add a string like "n<numeric ID>-<nick>" to avoid broadcasting private messages to everyone connected (although the flash client ignores PMs sent to others)
amfsend(&amf, sock);
- ++currentcolor;
+ // ++currentcolor;
free(msg);
continue;
}
@@ -470,7 +337,7 @@ int main(int argc, char** argv)
amfsend(&amf, sock);
}
// Items for privmsg: 0=cmd, 2=channel, 3=msg, 4=color/lang, 5=nick
- if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "privmsg") && amfin->items[3].type==AMF_STRING && amfin->items[5].type==AMF_STRING)
+ 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[5].type==AMF_STRING)
{
wchar_t* msg=fromnumlist(amfin->items[3].string.string);
// Timestamps, e.g. "[hh:mm] name: message"
@@ -478,7 +345,52 @@ int main(int argc, char** argv)
struct tm* t=localtime(×tamp);
printf("[%02i:%02i] %s: %ls\n", t->tm_hour, t->tm_min, amfin->items[5].string.string, msg);
free(msg);
+ fflush(stdout);
+ }
+ // users on channel entry. there's also a "joinsdone" command for some reason...
+ else if(amfin->itemcount>3 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "joins"))
+ {
+ printf("Currently online: ");
+ int i;
+ for(i = 3; i < amfin->itemcount-1; i+=2)
+ {
+ // a "numeric" id precedes each nick, i.e. i is the id, i+1 is the nick
+ if(amfin->items[i+1].type==AMF_STRING)
+ {
+ printf("%s%s", (i==3?"":", "), amfin->items[i+1].string.string);
+ }
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+ // join
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "join") && amfin->items[3].type==AMF_STRING)
+ {
+ // Timestamps, e.g. "[hh:mm] name: message"
+ time_t timestamp=time(0);
+ struct tm* t=localtime(×tamp);
+ printf("[%02i:%02i] %s entered the channel\n", t->tm_hour, t->tm_min, amfin->items[3].string.string);
+ fflush(stdout);
+ }
+ // part
+ else if(amfin->itemcount==4 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "quit") && amfin->items[3].type==AMF_STRING)
+ {
+ // Timestamps, e.g. "[hh:mm] name: message"
+ time_t timestamp=time(0);
+ struct tm* t=localtime(×tamp);
+ printf("[%02i:%02i] %s left the channel\n", t->tm_hour, t->tm_min, amfin->items[2].string.string);
+ fflush(stdout);
+ }
+ // nick
+ else if(amfin->itemcount==5 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "nick") && amfin->items[2].type==AMF_STRING && amfin->items[3].type==AMF_STRING)
+ {
+ // Timestamps, e.g. "[hh:mm] name: message"
+ time_t timestamp=time(0);
+ struct tm* t=localtime(×tamp);
+ printf("[%02i:%02i] %s changed nickname to %s\n", t->tm_hour, t->tm_min, amfin->items[2].string.string, amfin->items[3].string.string);
+ fflush(stdout);
}
+ // else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
// ++outnum; (Debugging)
diff --git a/irchack.c b/irchack.c
index fa527f8..b3ab620 100644
--- a/irchack.c
+++ b/irchack.c
@@ -1,7 +1,6 @@
/*
irchack, a simple application to reuse IRC clients as user interfaces for tc_client
- Copyright (C) 2014-2015 alicia@ion.nu
- Copyright (C) 2015 Jade Lea
+ Copyright (C) 2014 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
@@ -21,177 +20,68 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
-#include <sys/socket.h>
-#include <ctype.h>
-#include <signal.h>
-#ifdef __ANDROID__
-// Android has no dprintf, so we make our own
-#include <stdarg.h>
-size_t dprintf(int fd, const char* fmt, ...)
+int main()
{
- va_list va;
- va_start(va, fmt);
- int len=vsnprintf(0, 0, fmt, va);
- va_end(va);
- char buf[len+1];
- va_start(va, fmt);
- vsnprintf(buf, len+1, fmt, va);
- va_end(va);
- buf[len]=0;
- write(fd, buf, len);
- return len;
-}
-#endif
-
-// ANSI colors and their IRC equivalents
-struct color{const char* ansi; const char* irc;};
-struct color colortable[]={
- {"31", "\x03""05"},
- {"31;1", "\x03""04"},
- {"33", "\x03""07"},
- {"33", "\x03""07"},
- {"33;1", "\x03""08"},
- {"32;1", "\x03""09"},
- {"32;1", "\x03""09"},
- {"32", "\x03""03"},
- {"36", "\x03""10"},
- {"34;1", "\x03""12"},
- {"34;1", "\x03""12"},
- {"34", "\x03""02"},
- {"35", "\x03""06"},
- {"35;1", "\x03""13"},
- {"35;1", "\x03""13"},
- {"35;1", "\x03""13"}
-};
-
-const char* findcolor_irc(const char* ansi)
-{
- int len;
- for(len=0; ansi[len]&&ansi[len]!='m'; ++len);
- int i;
- for(i=0; i<16; ++i)
- {
- if(!strncmp(colortable[i].ansi, ansi, len) && !colortable[i].ansi[len])
- {
- return colortable[i].irc;
- }
- }
- return 0;
-}
-
-int findcolor_ansi(char* irc, char** end)
-{
- char color[4];
- strncpy(color, irc, 3);
- color[3]=0;
- if(!isdigit(color[1])){*end=&irc[1]; return -1;}
- if(!isdigit(color[2])){*end=&irc[2]; color[2]=color[1]; color[1]='0';}else{*end=&irc[3];}
- int i;
- for(i=0; i<16; ++i)
- {
- if(!strcmp(colortable[i].irc, color))
- {
- return i;
- }
- }
- return -1;
-}
-
-extern char session(int sock, const char* nick, const char* channel, const char* pass, const char* acc_user, const char* acc_pass);
-
-int main(int argc, char** argv)
-{
- int port=(argc>1?atoi(argv[1]):6667);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
- addr.sin_addr.s_addr=htonl(0x7f000001); // 127.0.0.1
- addr.sin_port=htons(port);
+ addr.sin_addr.s_addr=0;
+ addr.sin_port=htons(6667);
int lsock=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(bind(lsock, (struct sockaddr*)&addr, sizeof(addr))){perror("bind"); return 1;}
listen(lsock, 1);
- printf("Done! Open an IRC client and connect to localhost on port %i\n", port);
- signal(SIGCHLD, SIG_IGN);
- int sock;
- while((sock=accept(lsock, 0, 0))>-1)
+ int sock=accept(lsock, 0, 0);
+ close(lsock);
+ char buf[2048];
+ char* nick=0;
+ char* channel=0;
+ char* pass=0;
+ int len;
+ while(!nick || !channel) // Init loop, wait for USER, NICK and JOIN, well not USER
{
- if(fork()){continue;}
- char buf[2048];
- char* nick=0;
- char* channel=0;
- char* pass=0;
- char* acc_user=0;
- char* acc_pass=0;
- int len;
+ len=0;
while(1)
{
- len=0;
- int r;
- while(len<2047)
- {
- if((r=read(sock, &buf[len], 1))!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
- ++len;
- }
- if(r!=1){break;}
- buf[len]=0;
- if(!strncmp(buf, "USER ", 5))
- {
- acc_user=&buf[5];
- char* end=strchr(acc_user, ' ');
- if(end){end[0]=0;}
- acc_user=strdup(acc_user);
- }
- else if(!strncmp(buf, "PASS ", 5))
- {
- acc_pass=&buf[5];
- if(acc_pass[0]==':'){acc_pass=&acc_pass[1];}
- acc_pass=strdup(acc_pass);
- }
- else if(!strncmp(buf, "NICK ", 5))
- {
- char* newnick=&buf[5];
- if(newnick[0]==':'){newnick=&nick[1];}
- if(!nick)
- {
- dprintf(sock, ":irchack 001 %s :Welcome\n", newnick);
- }else{
- dprintf(sock, ":%s!user@host NICK :%s\n", nick, newnick);
- }
- free(nick);
- nick=strdup(newnick);
- }
- else if(nick && !strncmp(buf, "JOIN ", 5))
- {
- free(channel);
- channel=&buf[5];
- if(channel[0]==':'){channel=&channel[1];}
- free(pass);
- pass=strchr(channel, ' ');
- if(pass)
- {
- pass[0]=0;
- pass=&pass[1];
- if(pass[0]==':'){pass=strdup(&pass[1]);}
- }
- if(channel[0]=='#'){channel=&channel[1];}
- channel=strdup(channel);
- if(!session(sock, nick, channel, pass, acc_user, acc_pass)){break;}
- }
- else if(!strncmp(buf, "PING ", 5))
+ if(read(sock, &buf[len], 1)!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
+ ++len;
+ }
+ buf[len]=0;
+ if(!strncmp(buf, "NICK ", 5))
+ {
+ char* newnick=&buf[5];
+ if(newnick[0]==':'){newnick=&nick[1];}
+ if(!nick)
{
- dprintf(sock, ":irchack PONG %s\n", &buf[5]);
+ dprintf(sock, ":irchack 001 %s :Welcome\n", newnick);
+ }else{
+ dprintf(sock, ":%s!user@host NICK :%s\n", nick, newnick);
}
+ free(nick);
+ nick=strdup(newnick);
+ }
+ else if(!strncmp(buf, "JOIN ", 5))
+ {
+ free(channel);
+ channel=&buf[5];
+ if(channel[0]==':'){channel=&channel[1];}
+ free(pass);
+ pass=strchr(channel, ' ');
+ if(pass)
+ {
+ pass[0]=0;
+ pass=&pass[1];
+ if(pass[0]==':'){pass=strdup(&pass[1]);}
+ }
+ if(channel[0]=='#'){channel=&channel[1];}
+ channel=strdup(channel);
+ }
+ else if(!strncmp(buf, "PING ", 5))
+ {
+ dprintf(sock, ":irchack PONG %s\n", &buf[5]);
}
- shutdown(sock, SHUT_RDWR);
- _exit(0);
}
- close(lsock);
- return 0;
-}
-
-char session(int sock, const char* nick, const char* channel, const char* pass, const char* acc_user, const char* acc_pass)
-{
+ // We now have what we need to launch tc_client
printf("Nick: %s\n", nick);
printf("Channel: %s\n", channel);
printf("Password: %s\n", pass);
@@ -205,17 +95,13 @@ char session(int sock, const char* nick, const char* channel, const char* pass,
close(tc_out[0]);
dup2(tc_in[0], 0);
dup2(tc_out[1], 1);
- if(acc_user && acc_pass)
- {
- execl("./tc_client", "./tc_client", "-u", acc_user, "-p", acc_pass, channel, nick, pass, (char*)0);
- }else{
- execl("./tc_client", "./tc_client", channel, nick, pass, (char*)0);
- }
- perror("Failed to exec tc_client");
+ execl("./tc_client", "./tc_client", channel, nick, pass, (char*)0);
+ printf("Failed to exec tc_client\n");
_exit(1);
}
close(tc_in[0]);
close(tc_out[1]);
+ dprintf(tc_in[1], ":%s!user@host JOIN %s\n", nick, channel);
struct pollfd pfd[2];
pfd[0].fd=tc_out[0];
pfd[0].events=POLLIN;
@@ -223,9 +109,6 @@ char session(int sock, const char* nick, const char* channel, const char* pass,
pfd[1].fd=sock;
pfd[1].events=POLLIN;
pfd[1].revents=0;
- char buf[2048];
- int len;
- char joins=0;
while(1)
{
poll(pfd, 2, -1);
@@ -233,13 +116,11 @@ char session(int sock, const char* nick, const char* channel, const char* pass,
{
pfd[0].revents=0;
len=0;
- int r;
- while(len<2047)
+ while(1)
{
- if((r=read(tc_out[0], &buf[len], 1))!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
+ if(read(tc_out[0], &buf[len], 1)!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
++len;
}
- if(r!=1){break;}
buf[len]=0;
printf("Got from tc_client: '%s'\n", buf);
if(!strncmp(buf, "Currently online: ", 18))
@@ -254,63 +135,10 @@ printf("Got from tc_client: '%s'\n", buf);
user=next;
}
write(sock, "\n", 1);
- joins=1;
- continue;
- }
- else if(joins)
- {
dprintf(sock, ":irchack 366 %s #%s :End of /NAMES list.\n", nick, channel);
- joins=0;
- }
- if(!strncmp(buf, "Room topic: ", 12))
- {
- dprintf(sock, ":irchack 332 %s #%s :%s\n", nick, channel, &buf[12]);
- continue;
- }
- if(!strcmp(buf, "Password required"))
- {
- dprintf(sock, ":irchack 475 %s :Cannot join %s without the correct password\n", channel, channel);
- close(tc_in[1]);
- close(tc_out[0]);
- return 1;
- }
- if(!strncmp(buf, "Guest ID: ", 10))
- {
- dprintf(sock, ":%s!user@host NICK :guest-%s\n", nick, &buf[10]);
- continue;
- }
- if(!strncmp(buf, "No such nick: ", 14))
- {
- dprintf(sock, ":irchack 401 %s %s :No such nick/channel\n", nick, &buf[14]);
- continue;
- }
- char* space=strchr(buf, ' ');
- if(space && !strcmp(space, " is a moderator."))
- {
- space[0]=0;
- dprintf(sock, ":irchack MODE #%s +o %s\n", channel, buf);
- continue;
- }
- if(space && !strcmp(space, " is no longer a moderator."))
- {
- space[0]=0;
- dprintf(sock, ":irchack MODE #%s -o %s\n", channel, buf);
continue;
}
if(buf[0]!='['){continue;} // Beyond this we only care about timestamped lines
- // Translate ANSI escape codes to IRC color code instead
- char* ansi;
- const char* color="";
- while((ansi=strstr(buf, "\x1b[")))
- {
- int len;
- for(len=0; ansi[len]&&ansi[len]!='m'; ++len);
- if(ansi[len]=='m'){++len;}
- const char* c=findcolor_irc(&ansi[2]);
- if(c){color=c;}
- memmove(ansi, &ansi[len], strlen(&ansi[len])+1);
- }
-
char* name=strchr(buf, ' ');
if(!name){continue;}
name[0]=0;
@@ -326,11 +154,11 @@ printf("Got from tc_client: '%s'\n", buf);
msg=strchr(&msg[5], ' ');
if(!msg){continue;}
msg=&msg[1];
- dprintf(sock, ":%s!user@host PRIVMSG %s :%s%s\n", name, nick, color, msg);
+ dprintf(sock, ":%s!user@host PRIVMSG %s :%s\n", name, nick, msg);
}else{ // Regular channel message
- dprintf(sock, ":%s!user@host PRIVMSG #%s :%s%s\n", name, channel, color, msg);
+ dprintf(sock, ":%s!user@host PRIVMSG #%s :%s\n", name, channel, msg);
}
- }else{ // action, parse the actions and send them as JOINs, NICKs and QUITs etc. instead
+ }else{ // action, TODO: parse the actions and send them as JOINs, NICKs and QUITs etc. instead
msg[0]=0;
msg=&msg[1];
if(!strcmp(msg, "entered the channel"))
@@ -355,13 +183,13 @@ printf("Got from tc_client: '%s'\n", buf);
{
pfd[1].revents=0;
len=0;
- int r;
- while(len<2047)
+ while(1)
{
- if((r=read(sock, &buf[len], 1))!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
+ if(read(sock, &buf[len], 1)!=1 || buf[len]=='\r' || buf[len]=='\n'){break;}
++len;
}
- if(r!=1){break;}
+ if(len<=0){continue;}
+ while(len>0 && (buf[len-1]=='\n'||buf[len-1]=='\r')){--len;}
buf[len]=0;
printf("Got from IRC client: '%s'\n", buf);
if(!strncmp(buf, "PRIVMSG ", 8))
@@ -372,21 +200,6 @@ printf("Got from IRC client: '%s'\n", buf);
msg[0]=0;
msg=&msg[1];
if(msg[0]==':'){msg=&msg[1];}
- char* color;
- while((color=strchr(msg, '\x03')))
- {
- char* end;
- int c=findcolor_ansi(color, &end);
- if(c!=-1){dprintf(tc_in[1], "/color %i\n", c);}
- memmove(color, end, strlen(end)+1);
- }
- if(!strncmp(msg, "\x01""ACTION ", 8)) // Translate '/me'
- {
- msg=&msg[7];
- msg[0]='*';
- char* end=strchr(msg, '\x01');
- if(end){end[0]='*';}
- }
if(target[0]=='#' && !strcmp(&target[1], channel))
{
dprintf(tc_in[1], "%s\n", msg);
@@ -394,21 +207,13 @@ printf("Got from IRC client: '%s'\n", buf);
dprintf(tc_in[1], "/msg %s %s\n", target, msg);
}
}
- else if(!strncmp(buf, "NICK ", 5))
- {
- char* nick=&buf[5];
- if(nick[0]==':'){nick=&nick[1];}
- dprintf(tc_in[1], "/nick %s\n", nick);
- }
else if(!strncmp(buf, "PING ", 5))
{
dprintf(sock, ":irchack PONG %s\n", &buf[5]);
}
- else if(!strncmp(buf, "QUIT ", 5)){break;}
}
}
- close(tc_in[1]);
- close(tc_out[0]);
+ shutdown(sock, SHUT_RDWR);
return 0;
}