/******************************************************************** Copyright (C) 2001 Bassoukos Tassos This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *********************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "network.h" #include "protocol.h" #include "messages.h" #include "bookmarks.h" #include "tasks.h" #include "connection.h" #include "login.h" #include "smalltrans.h" #include "agreement.h" #include "news.h" #include "userlist.h" #include "chat.h" #include "guiprefs.h" #include "privs.h" #include "filelist.h" #include "files.h" #include "filetransfer.h" #include "privchat.h" #include "tasklist.h" #include "pixmap.h" /* ================================== */ typedef struct { Message *m; char *buf; int total_size; int current_bytes; } PendingRequest; typedef struct { int task; gpointer data; ConnectionMsgFunc start; } PendingReply; typedef struct { char *buf; int bufsize; Message *m; } IncomingMessage; static gboolean connection_do_write(GIOChannel *conn,GIOCondition cond,Connection *c); static gboolean connection_error_cancel(GIOChannel *chan,GIOCondition cond, Connection *c); /* ================================== */ enum { CONNECTED_SIGNAL, LAST_SIGNAL }; static GSList *connections_list=NULL; static guint connection_signals[LAST_SIGNAL]={0}; static GtkObjectClass *parent_class=NULL; static void connection_ended_request_helper(PendingRequest *p, gpointer dummy){ free(p->buf); message_unref(p->m); free(p); } static void connection_ended_reply_helper(PendingReply *p, gpointer dummy){ if(ft_test_reply_close(p->start) && ft_is_zombie(p->data)) free(p->data); } extern void main_window_update_title(void); static void connection_cleanup(Connection *c){ connections_list=g_slist_remove(connections_list,c); main_window_update_title(); if(c->rwatch!=-1){ g_source_remove(c->rwatch); c->rwatch=-1; } if(c->wwatch!=-1){ g_source_remove(c->wwatch); c->wwatch=-1; } if(c->errwatch!=-1){ g_source_remove(c->errwatch); c->errwatch=-1; } if(c->channel!=NULL){ g_io_channel_close(c->channel); g_io_channel_unref(c->channel); c->channel=NULL; } if(c->connection_fd!=-1){ close(c->connection_fd); c->connection_fd=-1; } if(c->bookmark!=NULL){ free_bookmark(c->bookmark); c->bookmark=NULL; } #if 0 // automagically done by gtk .... if(c->task!=NULL){ gtk_widget_destroy(GTK_WIDGET(c->task)); c->task=NULL; } #endif if(c->gui!=NULL){ gui_destroy(c); c->gui=NULL; } if(c->replies_pending){ g_list_foreach(c->replies_pending, (GFunc)connection_ended_reply_helper, NULL); g_list_free(c->replies_pending); c->replies_pending=NULL; } if(c->requests_pending){ g_list_foreach(c->requests_pending, (GFunc)connection_ended_request_helper, NULL); g_list_free(c->requests_pending); c->requests_pending=NULL; } if(c->rcvmsg!=NULL){ message_unref(c->rcvmsg); c->rcvmsg=NULL; } if(c->rcvbuffer!=NULL){ free(c->rcvbuffer); c->rcvbuffer=NULL; } c->rcvbuf_size=0; c->rcvbuf_cur=0; } static void connection_finalize(GtkObject *o){ connection_cleanup((Connection *)o); parent_class->finalize(o); } static void connection_destroy(GtkObject * o){ connection_cleanup((Connection *)o); parent_class->destroy(o); } static void connection_klass_init(ConnectionClass *klass){ GtkObjectClass *object_class=(GtkObjectClass*)klass; parent_class=gtk_type_class(GTK_TYPE_OBJECT); object_class->finalize=connection_finalize; object_class->destroy=connection_destroy; connection_signals[CONNECTED_SIGNAL]= gtk_signal_new("connected", GTK_RUN_LAST|GTK_RUN_NO_RECURSE, object_class->type, GTK_SIGNAL_OFFSET(ConnectionClass,connected), gtk_marshal_NONE__NONE, GTK_TYPE_NONE,0); gtk_object_class_add_signals(object_class,connection_signals,LAST_SIGNAL); klass->connected=NULL; } static void connection_init(Connection *c){ c->bookmark=NULL; c->connection_fd=-1; c->channel=NULL; c->rwatch=c->wwatch=c->errwatch=-1; c->task=NULL; c->version=0; c->server_socket_no=-1; c->requests_pending=NULL; c->replies_pending=NULL; c->gui=NULL; c->rcvmsg=NULL; c->rcvbuf_size=0; c->rcvbuf_cur=0; c->rcvbuffer=NULL; } GtkType connection_get_type(void){ static GtkType connection_type=0; if(connection_type==0){ static const GtkTypeInfo type_info={ "Connection", sizeof(Connection), sizeof(ConnectionClass), (GtkClassInitFunc) connection_klass_init, (GtkObjectInitFunc) connection_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; connection_type=gtk_type_unique(GTK_TYPE_OBJECT,&type_info); gtk_type_set_chunk_alloc(connection_type,8); } return connection_type; } /* ================================== */ void forall_connections(ConnectionFunc func,gpointer data){ g_slist_foreach(connections_list, (GFunc)func, data); } guint count_connections() { return g_slist_length(connections_list); } void connection_expect_reply(Connection *c, int tasknum, ConnectionMsgFunc callback, gpointer data){ PendingReply *p=(PendingReply *)malloc(sizeof(PendingReply)); p->start=callback; p->data=data; p->task=tasknum; c->replies_pending=g_list_append(c->replies_pending,p); } void connection_enq_send_buffer(Connection *c,char *buf,int size,Message *m){ PendingRequest *p=malloc(sizeof(PendingRequest)); p->buf=buf; p->current_bytes=0; p->total_size=size; p->m=m; message_ref(m); c->requests_pending=g_list_append(c->requests_pending,p); if(c->wwatch==-1) c->wwatch=g_io_add_watch(c->channel,G_IO_OUT, (GIOFunc)connection_do_write,c); } void connection_cancel(Connection *c){ gtk_object_destroy(GTK_OBJECT(c)); } static void handle_unknown_request(Connection *c,Message *m,gpointer data){ #ifdef DEBUG GList *l; int i; printf(_("Unknown Server-initiated transaction: info=%d type=%d task=%d\n"), m->w.info,m->w.type,m->w.task); printf(_(" object_count=%d;\n"),m->w.object_count); for(i=0,l=m->objects;l!=NULL;l=l->next,i++) printf(_(" %d.type=%3d;\n"),i,((HLObject *)l->data)->type); #endif message_unref(m); } static struct ServerMessage { int type; ConnectionMsgFunc handle; gpointer data; char *message; char *icon; } server_messages[]={ {HLSI_AGREEMENT,handle_server_agreement,NULL,N_("agreement"),HL_STOCK_PIXMAP_AGREEMENT}, {HLSI_NEWPOST,handle_server_new_post,NULL,N_("news post"),HL_STOCK_PIXMAP_NEWS_POST}, {HLSI_USERCHANGE,handle_user_change,NULL,NULL,NULL}, {HLSI_USERLEAVE,handle_user_leave,NULL,NULL,NULL}, {HLSI_DISCONNECTED,handle_server_disconnect, NULL,NULL, NULL}, /* FIXME: disconnect ourselves cleanly */ {HLSI_SELFUSER,handle_privs_sent,NULL,NULL,NULL}, {HLSI_BROADCAST,handle_server_message,NULL,NULL,NULL}, {HLSI_BROADCAST18, handle_server_message, NULL, NULL, NULL}, {HLSI_RELAYCHAT,chat_receive_mesasage,NULL,NULL,NULL}, {HLSI_SERVERQUPDATE,handle_ftq_update,NULL,NULL,NULL}, {HLSI_BANNERURL,handle_banner,NULL,NULL,NULL}, {HLSI_JOINEDPCHAT,privchat_handle_privchat,privchat_handle_other_join,NULL,NULL}, {HLSI_LEFTPCHAT,privchat_handle_privchat,privchat_handle_other_leave,NULL,NULL}, {HLSI_CHANGESUBJECT,privchat_handle_privchat,privchat_handle_subject_change,NULL,NULL}, {HLSI_INVITEDTOPCHAT,privchat_handle_privchat,privchat_handle_invite,NULL,NULL}, {0,handle_unknown_request,NULL,NULL,NULL} }; static struct ServerMessage *connection_get_servermessage(Connection *c,int type){ struct ServerMessage *sm=server_messages; for(;sm->type!=0;sm++) if(sm->type==type) break; return sm; } static void connection_get_task(Connection *c,Message *m,struct ServerMessage *sm){ char *msg=g_strdup_printf(_("Receiving %s"),sm->message); Task *task=task_new_for_connection(c,sm->icon); message_set_task(m,task,0.1,1.0); task_set_postext(task,0.1,msg); free(msg); gtk_signal_connect_object(GTK_OBJECT(m),"destroy", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(task)); } static void connection_handle_remote_request(Connection *c,Message *m){ struct ServerMessage *sm=connection_get_servermessage(c,m->w.type); if(!message_is_complete(m)){ if(sm->message!=NULL) connection_get_task(c,m,sm); } else { sm->handle(c,m,sm->data); } } static int compare_replies(PendingReply *a, guint16 *b){ return a->task - *b; } static void connection_handle_reply(Connection *c,Message *m){ PendingReply *pr=NULL; GList *l; if((l=g_list_find_custom(c->replies_pending, &m->w.task, (GCompareFunc)compare_replies))){ pr = l->data; } if(pr){ message_ref(m); pr->start(c,m,pr->data); if(message_is_complete(m)){ c->replies_pending = g_list_remove(c->replies_pending,pr); free(pr); } message_unref(m); } else { if(message_is_complete(m)){ printf(_("Unexpected reply to task %d"),m->w.task); message_unref(m); } } } static gboolean connection_do_read(GIOChannel *conn,GIOCondition cond,Connection *c){ Message *m; while(TRUE){ if(c->rcvbuffer==NULL) { if(c->rcvmsg==NULL){ c->rcvbuf_size=SIZE_OF_WIRETRANSACTION; } else { c->rcvbuf_size=c->rcvmsg->w.data_length; } c->rcvbuf_cur=0; c->rcvbuffer=malloc(c->rcvbuf_size+4); } while(c->rcvbuf_currcvbuf_size){ int size=read(c->connection_fd, c->rcvbuffer+c->rcvbuf_cur, c->rcvbuf_size-c->rcvbuf_cur); if(size<0) return TRUE; if(!size) { connection_error_cancel(c->channel, G_IO_HUP, c); return FALSE; } c->rcvbuf_cur+=size; if(c->rcvmsg!=NULL) message_set_pos(c->rcvmsg,(gfloat)c->rcvbuf_cur/(gfloat)c->rcvbuf_size); } if(c->rcvmsg==NULL){ c->rcvmsg=message_new_from_connection(c); } else { message_recv_objects(c->rcvmsg); } m=c->rcvmsg; if(message_is_complete(m)) c->rcvmsg=NULL; if(!m->w.type) { connection_handle_reply(c,m); } else { connection_handle_remote_request(c,m); } if(c->rcvbuffer!=NULL) free(c->rcvbuffer); c->rcvbuffer=NULL; c->rcvbuf_size=0; c->rcvbuf_cur=0; } return TRUE; } static gboolean connection_do_write(GIOChannel *conn,GIOCondition cond,Connection *c){ PendingRequest *pe; int count; while(c->requests_pending){ pe=c->requests_pending->data; while(pe->current_bytestotal_size){ count=write(c->connection_fd, pe->buf+pe->current_bytes, pe->total_size-pe->current_bytes); if(count<=0) return TRUE; pe->current_bytes+=count; if(pe->m) message_set_pos(pe->m,(gfloat)pe->current_bytes/(gfloat)pe->total_size); } c->requests_pending=g_list_remove(c->requests_pending,pe); message_unref(pe->m); free(pe->buf); free(pe); } c->wwatch=-1; return FALSE; } static gboolean connection_handshake_write(GIOChannel *conn,GIOCondition cond,Connection *c){ int err; socklen_t size=sizeof(err); if(getsockopt(c->connection_fd,SOL_SOCKET,SO_ERROR,&err,&size)!=0 || err!=0){ show_error(c,NULL,_("Could not connect to %s:%d : %s"), c->bookmark->host, c->bookmark->port, g_strerror(err)); connection_cancel(c); return TRUE; } task_set_printf(c->task,0.50,_("Handshaking...")); write(c->connection_fd,"TRTPHOTL\00\01\00\02",12); c->wwatch=-1; return FALSE; } static gboolean connection_handshake_read(GIOChannel *conn,GIOCondition cond,Connection *c){ char buf[8]; if(read(c->connection_fd,buf,8)!=8 || memcmp(buf,"TRTP\0\0\0\0",8)!=0){ show_error(c,NULL,_("%s is not a hotline server!"),c->bookmark->host); connection_cancel(c); return TRUE; } c->rwatch=g_io_add_watch(c->channel,G_IO_IN, (GIOFunc)connection_do_read,c); task_set_printf(c->task,0.75,_("Logging in...")); start_login(c); return FALSE; } static gboolean connection_error_cancel(GIOChannel *chan,GIOCondition cond, Connection *c){ int err; socklen_t size=sizeof(err); if(getsockopt(c->connection_fd,SOL_SOCKET,SO_ERROR,&err,&size)!=0 || err!=0){ show_error(NULL,NULL,_("%s: Connection closed: %s"), C_NAME(c), g_strerror(err)); } else { show_error(NULL, NULL, _("%s: server closed connection"), C_NAME(c)); } connection_cancel(c); return TRUE; } static gboolean connection_do_connect(Connection *c){ NetAddr na; if(isIP(&na,c->bookmark->host)==FALSE){ if(resolveHost(&na,c->bookmark->host)==FALSE){ show_error_errno(c,NULL,_("No IP for name %s:"),c->bookmark->host); return FALSE; } } task_set_printf(c->task,0.25,_("Connecting to %s..."),inet_ntoa(na)); c->connection_fd=getAsyncSocketTo(&na,c->bookmark->port); if(c->connection_fd==-1){ show_error_errno(c,NULL,_("Could not connect to %s:%d :"), c->bookmark->host,c->bookmark->port); return FALSE; } return TRUE; } void connection_new_to(Bookmark *b){ Connection *c; c=gtk_type_new(CONNECTION_TYPE); c->bookmark=dup_bookmark(b); c->task=task_new_for_connection(c,HL_STOCK_PIXMAP_CONNECT); gtk_signal_connect_object(GTK_OBJECT(c->task),"cancelled", GTK_SIGNAL_FUNC(connection_cancel),GTK_OBJECT(c)); task_set_printf(c->task,-1.0, _("Resolving %s ..."),b->host); if(!connection_do_connect(c)) { connection_cancel(c); return; } c->channel=g_io_channel_unix_new(c->connection_fd); c->errwatch=g_io_add_watch(c->channel,G_IO_ERR|G_IO_HUP|G_IO_PRI|G_IO_NVAL, (GIOFunc)connection_error_cancel,c); c->rwatch=g_io_add_watch(c->channel,G_IO_IN, (GIOFunc)connection_handshake_read,c); c->wwatch=g_io_add_watch(c->channel,G_IO_OUT, (GIOFunc)connection_handshake_write,c); connections_list=g_slist_append(connections_list,c); }