#define _GNU_SOURCE #include #include #include #include #include #include #include #include "weechat-plugin.h" #include "../ssh/tomsg_clientlib.h" #include "global.h" #include "debug.h" WEECHAT_PLUGIN_NAME("tomsg") WEECHAT_PLUGIN_DESCRIPTION("tomsg client plugin") WEECHAT_PLUGIN_AUTHOR("Tom Smeding") WEECHAT_PLUGIN_VERSION("0.1") WEECHAT_PLUGIN_LICENSE("MIT") WEECHAT_PLUGIN_PRIORITY(1000) static const char *errpfx, *netpfx; struct roomdata { char *name; struct t_gui_buffer *buffer; struct t_gui_nick_group *buffer_nickgroup; int nmembers; struct conndata *conn; // do not free }; struct conndata { int fd; struct t_hook *fd_hook; struct t_gui_buffer *buffer; bool negotiation_complete; int nrooms, roomscap; struct roomdata **rooms; char *username, *pending_username; i64 linebuf_sz, linebuf_len; char *linebuf; }; static struct t_weechat_plugin *weechat_plugin; static struct t_hashtable *conntable; static void room_update_attributes(struct roomdata *room){ bool private=room->nmembers<=2; weechat_buffer_set(room->buffer,"localvar_set_type",private?"private":"channel"); weechat_buffer_set(room->buffer,"notify",private?"3":"1"); debugf("room_update_attributes: set private for room %s to %d\n",room->name,private); if(room->conn->username){ weechat_buffer_set(room->buffer,"localvar_set_nick",room->conn->username); } else { weechat_buffer_set(room->buffer,"localvar_del_nick",""); } } static void close_room(struct roomdata *room){ debugf("close_room(room=%p)\n",room); free(room->name); if(room->buffer)weechat_buffer_close(room->buffer); free(room); } static void message_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("message_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_ERROR){ weechat_printf(conn->buffer,"tomsg: send threw error: %s",res.error); } else if(res.type!=NET_NUMBER){ debugf("message_net_callback: res.type=%d\n",res.type); } } static int room_input_cb(const void *room_vp,void *_d,struct t_gui_buffer *buffer,const char *input){ (void)_d; (void)buffer; struct roomdata *room=(struct roomdata*)room_vp; struct conndata *conn=room->conn; const char *tosend; const char *p=strchr(input,'\n'); bool skipfirst=input[0]=='/'&&input[1]=='/'; bool free_tosend=false; if(p!=NULL){ debugf("room_input_cb: input contained newline <%s>\n",input); tosend=strdup(input+skipfirst); *strchr(tosend,'\n')='\0'; free_tosend=true; } else { tosend=input+skipfirst; } net_sendf(conn->fd,message_net_callback,NULL,"send %s %s",room->name,tosend); weechat_printf(room->buffer,"%s\t%s",conn->username,tosend); if(free_tosend){ free((void*)tosend); } return WEECHAT_RC_OK; } static int room_close_cb(const void *room_vp,void *_d,struct t_gui_buffer *buffer){ (void)_d; (void)buffer; struct roomdata *room=(struct roomdata*)room_vp; room->buffer=NULL; return WEECHAT_RC_OK; } static void create_room_buffer(struct roomdata *room){ if(room->buffer!=NULL){ weechat_buffer_close(room->buffer); } room->buffer= weechat_buffer_new(room->name, room_input_cb,room,NULL, room_close_cb,room,NULL); weechat_buffer_set(room->buffer,"nicklist","1"); weechat_buffer_set(room->buffer,"nicklist_case_sensitive","1"); weechat_buffer_set(room->buffer,"nicklist_display_groups","0"); room->buffer_nickgroup=NULL; room_update_attributes(room); } static void history_net_callback(int fd,struct net_response res,void *payload){ struct roomdata *room=(struct roomdata*)payload; assert(room); debugf("history_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); } struct room_and_name{ struct roomdata *room; char *name; }; static void isonline_net_callback(int fd,struct net_response res,void *payload){ (void)fd; debugf("isonline_net_callback(fd=%d,res={.type=%d,.number=%" PRIi64 "})\n", fd,res.type,res.number); const char *color= res.type!=NET_NUMBER ? "red" : res.number >= 1 ? "weechat.color.chat_nick" : "weechat.color.nicklist_away"; struct room_and_name *rn=(struct room_and_name*)payload; struct roomdata *room=rn->room; char *name=rn->name; free(rn); struct t_gui_nick *nickp=weechat_nicklist_search_nick(room->buffer,room->buffer_nickgroup,name); free(name); if(nickp==NULL)return; weechat_nicklist_nick_set(room->buffer,nickp,"color",color); } static void members_net_callback(int fd,struct net_response res,void *payload){ struct roomdata *room=(struct roomdata*)payload; assert(room); debugf("members_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); if(res.type!=NET_LIST){ debugf("members_net_callback: res.type=%d\n",res.type); return; } if(room->buffer_nickgroup!=NULL){ weechat_nicklist_remove_all(room->buffer); } room->buffer_nickgroup= weechat_nicklist_add_group(room->buffer,NULL,"members","weechat.color.nicklist_group",1); for(int i=0;ibuffer,room->buffer_nickgroup, res.items[i],"weechat.color.chat_nick", "","weechat.color.chat_nick", 1); struct room_and_name *payload=malloc(sizeof(struct room_and_name)); assert(payload); payload->room=room; payload->name=strdup(res.items[i]); net_sendf(fd,isonline_net_callback,payload,"is_online %s",res.items[i]); } room->nmembers=res.nitems; room_update_attributes(room); } static void push_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("push_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_MESSAGE||res.type==NET_HISTORY||res.type==NET_JOIN||res.type==NET_INVITE){ i64 roomi; for(roomi=0;roominrooms;roomi++){ if(strcmp(conn->rooms[roomi]->name,res.room)==0){ break; } } if(roomi==conn->nrooms){ debugf("push_net_callback: message to unknown room '%s'\n",res.room); return; } struct roomdata *room=conn->rooms[roomi]; if(room->buffer==NULL){ create_room_buffer(room); } if(res.type==NET_MESSAGE){ bool private=room->nmembers<=2; weechat_printf_date_tags( room->buffer,res.timestamp/1000000LL,private?"notify_private":"notify_message", "%s\t%s",res.username,res.message); } else if(res.type==NET_HISTORY){ weechat_printf_date_tags( room->buffer,res.timestamp/1000000LL,NULL, "%s\t%s",res.username,res.message); } else if(res.type==NET_JOIN){ weechat_printf(room->buffer,"%sUser %s joined this room",netpfx,res.username); if(room->buffer_nickgroup){ weechat_nicklist_add_nick( room->buffer,room->buffer_nickgroup, res.username,"weechat.color.chat_nick", "","weechat.color.chat_nick", 1); } room->nmembers++; room_update_attributes(room); } else if(res.type==NET_INVITE){ weechat_printf(room->buffer,"%sYou were invited into this room",netpfx); net_sendf(fd,history_net_callback,room,"history %s 10",room->name); } else { assert(false); } } else if(res.type==NET_PONG){ // ok } else if(res.type==NET_ONLINE){ debugf(" NET_ONLINE with username='%s' num='%" PRIi64 "'\n",res.online.username,res.online.num); const char *color=res.online.num>0 ? "default" : "weechat.color.nicklist_away"; for(i64 i=0;inrooms;i++){ struct t_gui_nick *nickp=weechat_nicklist_search_nick( conn->rooms[i]->buffer,conn->rooms[i]->buffer_nickgroup,res.online.username); if(nickp!=NULL){ weechat_nicklist_nick_set(conn->rooms[i]->buffer,nickp,"color",color); } } } else { debugf("push_net_callback: unknown response type %d\n",res.type); } } static void history_push_net_callback(int fd,struct net_response res,void *payload){ debugf("history_ -> "); push_net_callback(fd,res,payload); } static void roomlist_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("roomlist_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_LIST){ for(i64 i=0;inrooms;i++){ close_room(conn->rooms[i]); } if(conn->roomscaproomscap=res.nitems+1; conn->rooms=realloc(conn->rooms,conn->roomscap*sizeof(struct roomdata*)); assert(conn->rooms); } conn->nrooms=res.nitems; for(i64 i=0;irooms[i]=malloc(sizeof(struct roomdata)); assert(room); room->name=strdup(res.items[i]); room->conn=conn; room->buffer=NULL; create_room_buffer(room); net_sendf(fd,history_net_callback,room,"history %s 10",room->name); net_sendf(fd,members_net_callback,room,"list_members %s",room->name); } } else { debugf("roomlist_net_callback: res.type=%d\n",res.type); } } static void login_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("login_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_OK){ if(conn->username)free(conn->username); conn->username=conn->pending_username; conn->pending_username=NULL; weechat_printf(conn->buffer,"Successfully logged in"); net_sendf(conn->fd,roomlist_net_callback,NULL,"list_rooms"); } else if(res.type==NET_ERROR){ weechat_printf(conn->buffer,"Error logging in: %s",res.error); } else { debugf("login_net_callback: res.type=%d\n",res.type); } } static void pong_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("pong_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_PONG){ weechat_printf(conn->buffer,"pong"); } else { debugf("pong_net_callback: res.type=%d\n",res.type); } } static void conn_destroy(struct conndata *conn){ debugf("conn_destroy(conn=%p (fd=%d))\n",conn,conn->fd); weechat_unhook(conn->fd_hook); if(conntable)weechat_hashtable_remove(conntable,&conn->fd); for(int i=0;inrooms;i++){ close_room(conn->rooms[i]); } if(conn->username)free(conn->username); if(conn->pending_username)free(conn->pending_username); free(conn->rooms); free(conn->linebuf); close(conn->fd); free(conn); } static int fd_hook_callback(const void *conn_vp,void *_d,int fd){ (void)_d; struct conndata *conn=(struct conndata*)conn_vp; debugf("fd_hook_callback(conn=%p (fd=%d))\n",conn,fd); assert(fd==conn->fd); if(conn->linebuf_len>conn->linebuf_sz/2){ conn->linebuf_sz*=2; conn->linebuf=realloc(conn->linebuf,conn->linebuf_sz); assert(conn->linebuf); } i64 nr; while(true){ nr=recv(fd,conn->linebuf+conn->linebuf_len,conn->linebuf_sz-conn->linebuf_len,0); if(nr<=0){ if(nr<0){ if(errno==EINTR)continue; if(errno==EAGAIN)return WEECHAT_RC_OK; // next time around maybe? debugf("fd_hook_callback: recv() < 0: %s\n",strerror(errno)); } else { debugf("fd_hook_callback: recv() == 0 (EOF)\n"); } weechat_printf(NULL,"tomsg: Connection dropped"); weechat_buffer_close(conn->buffer); return WEECHAT_RC_OK; } break; } conn->linebuf_len+=nr; while(true){ char *p=memchr(conn->linebuf,'\n',conn->linebuf_len); if(p==NULL)break; *p='\0'; i64 lenp1=p-conn->linebuf+1; net_handle_recv(conn->fd,conn->linebuf); memmove(conn->linebuf,conn->linebuf+lenp1,conn->linebuf_len-lenp1); conn->linebuf_len-=lenp1; } return WEECHAT_RC_OK; } static int conn_input_cb(const void *conn_vp,void *_d,struct t_gui_buffer *buffer,const char *input){ (void)_d; struct conndata *conn=(struct conndata*)conn_vp; debugf("conn_input_cb(conn=%p,buffer=%p,input=\"%s\")\n",conn,buffer,input); if(!conn->negotiation_complete){ weechat_printf(conn->buffer,"Server protocol version not yet negotiated, please wait..."); return WEECHAT_RC_OK; } char *input2=strdup(input); assert(input2); char *cursor=input2; char *cmd=strsep(&cursor," "); if(cmd==NULL){ free(input2); return WEECHAT_RC_OK; } if(strcmp(cmd,"login")==0){ char *username=strsep(&cursor," "); char *password=cursor; if(conn->pending_username)free(conn->pending_username); conn->pending_username=strdup(username); net_sendf(conn->fd,login_net_callback,NULL,"login %s %s",username,password); } else if(strcmp(cmd,"ping")==0){ net_sendf(conn->fd,pong_net_callback,NULL,"ping"); } else { weechat_printf(conn->buffer,"Unknown command '%s'",cmd); } return WEECHAT_RC_OK; } static bool password_hide_modifier__found; static void password_hide_modifier_hmapper(void *data,struct t_hashtable *ht,const void *key,const void *value){ (void)ht; (void)key; const struct conndata *conn=(const struct conndata*)value; if(conn->buffer==(struct t_gui_buffer*)data){ password_hide_modifier__found=true; } } static char* password_hide_modifier(const void *_p,void *_d,const char *modifier,const char *bufstr,const char *str){ (void)_p; (void)_d; (void)modifier; struct t_gui_buffer *buffer=(struct t_gui_buffer*)strtoll(bufstr,NULL,16); if(buffer==NULL){ debugf("password_hide_modifier: bufstr is NULL: '%s'\n",bufstr); return NULL; } password_hide_modifier__found=false; weechat_hashtable_map(conntable,password_hide_modifier_hmapper,buffer); if(!password_hide_modifier__found)return NULL; bool cursor_in_login=false; for(int i=0,j=0;i<6;i++,j++){ if(str[j]==0x19){ j+=3; cursor_in_login=true; } if(str[j]!="login "[i]){ return NULL; } } const char *p=strchr(str+6+3*cursor_in_login,' '); if(p==NULL)return NULL; char *newstr=malloc(p-str+8); assert(newstr); memcpy(newstr,str,p-str); if(memchr(newstr,0x19,p-str)!=NULL){ memcpy(newstr+(p-str)," ***",5); // count includes '\0' } else { memcpy(newstr+(p-str)," ***\x19" "b#",8); // count includes '\0' } return newstr; } static int conn_close_cb(const void *conn_vp,void *_d,struct t_gui_buffer *buffer){ (void)_d; (void)buffer; struct conndata *conn=(struct conndata*)conn_vp; debugf("conn_close_cb(conn=%p,buffer=%p) fd=%d\n",conn,buffer,conn->fd); conn_destroy(conn); return WEECHAT_RC_OK; } static void version_net_callback(int fd,struct net_response res,void *payload){ (void)payload; debugf("version_net_callback(fd=%d,res={.type=%d})\n",fd,res.type); struct conndata *conn=weechat_hashtable_get(conntable,&fd); assert(conn); if(res.type==NET_OK){ conn->negotiation_complete=true; weechat_printf(conn->buffer,"Version negotiation complete."); } else { conn_destroy(conn); weechat_printf(NULL,"tomsg: Server has incompatible protocol version (we want 1)!"); } } static int connect_cb(const void *_p,void *hostname,int status,int _g,int fd,const char *err,const char *_i){ (void)_p; (void)_g; (void)_i; switch(status){ case WEECHAT_HOOK_CONNECT_OK: { struct conndata *conn=malloc(sizeof(struct conndata)); assert(conn); conn->fd=fd; conn->fd_hook=weechat_hook_fd(fd,1,0,0,fd_hook_callback,conn,NULL); conn->buffer=weechat_buffer_new((char*)hostname,conn_input_cb,conn,NULL,conn_close_cb,conn,NULL); conn->negotiation_complete=false; conn->nrooms=0; conn->roomscap=2; conn->rooms=malloc(conn->roomscap*sizeof(struct roomdata)); assert(conn->rooms); conn->username=NULL; conn->pending_username=NULL; conn->linebuf_sz=512; conn->linebuf_len=0; conn->linebuf=malloc(conn->linebuf_sz); assert(conn->linebuf); weechat_printf(conn->buffer,"Connected!"); weechat_hashtable_set(conntable,&fd,conn); net_sendf(fd,version_net_callback,NULL,"version 1"); return WEECHAT_RC_OK; } default: weechat_printf(NULL,"%stomsg: Could not connect to %s: %s",errpfx,(char*)hostname,err); return WEECHAT_RC_ERROR; } } static int cmd_tomsg_cb(const void *_pointer, void *_data, struct t_gui_buffer *buffer, int argc, char **argv, char **_argv_eol) { (void)_pointer; (void)_data; (void)_argv_eol; if (argc < 2) { weechat_printf(NULL, "%stomsg: Invalid number of arguments to /tomsg", errpfx); return WEECHAT_RC_ERROR; } if (strcmp(argv[1], "connect") == 0) { if (argc < 3 || argc > 4) { weechat_printf(NULL, "%stomsg: Invalid number of arguments to /tomsg connect", errpfx); return WEECHAT_RC_ERROR; } const char *hostname = argv[2]; if (strlen(hostname) == 0) { weechat_printf(buffer, "%stomsg: Invalid hostname", errpfx); return WEECHAT_RC_ERROR; } int port = 2222; if (argc == 4) { char *endp; port = strtol(argv[3], &endp, 10); if (argv[3][0] == '\0' || *endp != '\0' || port <= 0 || port >= 65536) { weechat_printf(NULL, "%sInvalid port number", errpfx); return WEECHAT_RC_ERROR; } } debugf("Connecting to %s:%d\n", hostname, port); struct tomsg_async_connect *async; enum tomsg_retval ret = tomsg_async_connect(hostname, port, &async); if (ret != TOMSG_OK) { weechat_printf(NULL, "%stomsg: Cannot start connecting to %s:%d: %s\n", errpfx, hostname, port, tomsg_strerror(ret)); return WEECHAT_RC_OK; } weechat_hook_connect( NULL, hostname,port,1,0, NULL,NULL,0,NULL, NULL, connect_cb,NULL,hostname_copy); } else { weechat_printf(NULL, "%stomsg: Unknown command \"%s\" to /tomsg", errpfx, argv[1]); return WEECHAT_RC_ERROR; } return WEECHAT_RC_OK; } int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char **argv){ (void)argc; (void)argv; weechat_plugin = plugin; debug_init(); errpfx = weechat_prefix("error"); netpfx = weechat_prefix("network"); weechat_hook_command( "tomsg", "Execute commands related to tomsg.", "connect [port]", " connect: Connect to a tomsg server", NULL, cmd_tomsg_cb, NULL, NULL); weechat_hook_modifier("input_text_display_with_cursor", password_hide_modifier, NULL, NULL); conntable = weechat_hashtable_new( 16, WEECHAT_HASHTABLE_INTEGER, WEECHAT_HASHTABLE_POINTER, NULL, NULL); return WEECHAT_RC_OK; } int weechat_plugin_end(struct t_weechat_plugin *plugin) { (void)plugin; weechat_hashtable_free(conntable); conntable = NULL; debug_deinit(); return WEECHAT_RC_OK; }