/* This file is part of Bolixo. Bolixo 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 3 of the License, or (at your option) any later version. Bolixo 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 Bolixo. If not, see . */ /* websocket client using CONNECT_HTTP_INFO. Works with TCPSERVER. */ #include #include #include #include #include #include #include #include #include #include #include #include #define INSTRUMENT_EXTERN #include "instrument.h" #include #include "websocket-client.h" using namespace std; #define webapi_test_NOTNEED #define webapi_addfile_NOTNEED #define webapi_addfile_bob_NOTNEED #define webapi_appendfile_NOTNEED #define webapi_delfile_NOTNEED #define webapi_undelete_NOTNEED #define webapi_modifyfile_NOTNEED #define webapi_modifyfile_bob_NOTNEED #define webapi_rename_NOTNEED #define webapi_copy_NOTNEED #define webapi_readfile_NOTNEED #define webapi_readfile_bob_NOTNEED #define webapi_readmore_NOTNEED #define webapi_mkdir_NOTNEED #define webapi_rmdir_NOTNEED #define webapi_listdir_NOTNEED #define webapi_stat_NOTNEED #define webapi_set_access_NOTNEED #define webapi_markview_NOTNEED #define webapi_list_inboxes_NOTNEED #define webapi_list_msgs_NOTNEED #define webapi_sendmsg_NOTNEED #define webapi_sendmsg_project_NOTNEED #define webapi_replymsg_NOTNEED #define webapi_replymsg_project_NOTNEED #define webapi_sendattach_NOTNEED #define webapi_sendtalk_NOTNEED #define webapi_sendtalk_file_NOTNEED #define webapi_list_talk_NOTNEED #define webapi_public_listdir_NOTNEED #define webapi_public_readfile_NOTNEED #define webapi_public_list_talk_NOTNEED #define webapi_systempubkey_NOTNEED #define webapi_verifysign_NOTNEED #define webapi_getpubkey_NOTNEED #define webapi_registernode_NOTNEED #define webapi_remotelogin_NOTNEED #define webapi_remotepass_NOTNEED #define webapi_remote_interest_set_NOTNEED #define webapi_remote_interest_unset_NOTNEED #define webapi_nodelogin_NOTNEED #define webapi_nodepass_NOTNEED #define webapi_config_read_NOTNEED #define webapi_config_write_NOTNEED #define webapi_contact_request_NOTNEED #define webapi_contact_manage_NOTNEED #define webapi_contact_list_NOTNEED #define webapi_list_contacts_NOTNEED #define webapi_list_lists_NOTNEED #define webapi_list_groups_NOTNEED #define webapi_create_group_NOTNEED #define webapi_delete_group_NOTNEED #define webapi_delete_list_NOTNEED #define webapi_set_member_NOTNEED #define webapi_contact_remove_NOTNEED #define webapi_list_members_NOTNEED #define webapi_create_group_list_NOTNEED #define webapi_set_group_NOTNEED #define webapi_playstep_NOTNEED #define webapi_playstep_more_NOTNEED #define webapi_login_NOTNEED #include "proto/webapi.protoch" static DEBUG_KEY D_HEADER("header","Print request header"); static DEBUG_KEY D_PROTO("proto","HTTP protocol"); static void bo_websocket_send (CONNECT_HTTP_INFO &con, unsigned opcode, PARAM_STRING msg) { size_t lenmsg = strlen(msg.ptr); size_t lenframe = 2+2+4+lenmsg; // Longest frame for message < 65536 char buf[lenframe]; buf[0] = 128+opcode; char *ptmask = buf+2; if (lenmsg <= 125){ buf[1] = (char)lenmsg; lenframe = 6 + lenmsg; }else if (lenmsg < 65536){ buf[1] = 126; buf[2] = (lenmsg >> 8) &0xff; buf[3] = lenmsg & 0xff; ptmask = buf+4; }else{ tlmp_error ("bo_websocket_send lenmsg=%zu not supported",lenmsg); return; } buf[1] |= 0x80; // There is a 4 bytes mask char *ptbuf = ptmask+4; long long usec = fdpass_getnow(); ptmask[0] = usec & 0xff; ptmask[1] = (usec >> 8) &0xff; ptmask[2] = (usec >> 16) & 0xff; ptmask[3] = (usec >> 24) & 0xff; for (unsigned i=0; i 4){ bool fin = (line[0] &0x80) != 0; if (!fin){ tlmp_error ("fin != %d",fin); return 0; } unsigned opcode = line[0] &0xf; if (opcode == 8){ // Close // }else{ bool mask = (line[1]&0x80)!=0; unsigned len1 = line[1] &0x7f; unsigned data_len = len1; int frame_len = 2 + len1; const unsigned char *data = line+2; const unsigned char *maskb = line+2; if (len1 == 126){ data_len = (line[2]<<8)+line[3]; frame_len = 2 + 2 + data_len; data = line + 4; maskb = line + 4; } if (mask){ frame_len += 4; data += 4; } if (frame_len <= len){ ret = frame_len; //FILE *fout = fopen ("/tmp/log","a"); //fprintf (fout,"data:"); if (mask){ for (unsigned i=0; i void HANDLE_WSS::process( bool &endclient, bool &now_running, // Will be true if the websocket received the confirmation header and is not running. int clientfd, // Once the websocket is running, we simply copy to this handle. std::string *msg) // or we copy the message here, if not null { glocal HANDLE_WSS *wss = this; glocal msg; glocal now_running; glocal initmsgs; now_running = false; char buf[100*1024]; if (clientfd == -1 && msg == nullptr){ endclient = true; tlmp_error ("websocket-client: clientfd == -1 and msg == nullptr"); return; } int ret = con.receive (buf,sizeof(buf)); if (0 && con.ssl != nullptr){ int ssl_error = SSL_get_error(con.ssl,ret); int pending = SSL_pending(con.ssl); printf ("ssl_error=%d pending=%d\n",ssl_error,pending); } // printf ("receive ret=%d\n",ret); if (ret <= 0){ debug_printf (D_PROTO,"fill ret=%d %d(%s)\n",ret,errno,strerror(errno)); endclient = true; }else if (clientfd != -1 && state == WSS_RUNNING){ //tlmp_warning ("write to clientfd ret=%d",ret); ::write (clientfd,buf,ret); }else{ (sbuf,buf,ret); int ret = 0; auto line = (const char *)buf; if (glocal.wss->state == WSS_IN_HEADER){ // We parse lines until the end of the header auto pt = line; int offset = 0; string hline; while (offset < len){ offset++; auto car = *pt++; if (is_any_of(car,'\r','\n')){ hline = string(line,offset-1); if (*pt == '\n'){ pt++; offset++; } ret = offset; break; } } if (ret > 0){ //tlmp_warning ("line=%s\n",hline.c_str()); const char *pt; if (hline.size() == 0){ glocal.now_running = true; glocal.wss->state = WSS_RUNNING; for (auto &m:glocal.initmsgs) glocal.wss->send (m); }else if (is_start_any_ofnc(hline,pt,"Sec-WebSocket-Accept:")){ pt = str_skip(pt); string tmp = glocal.wss->challenge + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; unsigned char* sha_str = SHA1(reinterpret_cast(tmp.c_str()), tmp.length(), nullptr); string acceptkey = base64_encode(reinterpret_cast(sha_str), 20); if (acceptkey != pt){ tlmp_warning ("websocket: Invalid challenge %s(%zu) <> %s(%zu)\n",acceptkey.c_str(),acceptkey.size(),pt,strlen(pt)); } } } }else if (glocal.wss->state == WSS_RUNNING){ // This is a websocket packet, potentially incomplete //tlmp_warning ("running len=%d\n",len); ret = bo_websocket_receive (glocal.wss->con,buf,len,*glocal.msg); //if (glocal.msg->size() > 0) tlmp_warning ("msg=%s\n",glocal.msg->c_str()); } //printf ("ret=%d len=%d\n",ret,len); return ret; } } void HANDLE_WSS::process( bool &endclient, bool &now_running, // Will be true if the websocket received the confirmation header and is not running. int clientfd) // Once the websocket is running, we simply copy to this handle. { process (endclient,now_running,clientfd,nullptr); } void HANDLE_WSS::process( bool &endclient, bool &now_running, // Will be true if the websocket received the confirmation header and is not running. std::string &msg) // Will contain the message received { process (endclient,now_running,-1,&msg); } void HANDLE_WSS::sendheader (PARAM_STRING hostname) { string buf; buf = string_f ("GET /wss HTTP/1.1\r\nhost: %s\r\nUser-Agent: bo-webtest\r\n",hostname.ptr); if (session.size() > 0){ buf += string_f ("cookie: session=%s;\r\n",session.c_str()); } buf += "Upgrade: websocket\r\n"; buf += "Connection: Upgrade\r\n"; challenge = string_f ("%Ld",fdpass_getnow()); buf += string_f("Sec-WebSocket-Key: %s\r\n",challenge.c_str()); buf += "Sec-WebSocket-Version: 13\r\n"; buf += "\r\n"; con.send (buf.c_str()); debug_printf (D_HEADER,"%s-----\n",buf.c_str()); } void HANDLE_WSS::send (PARAM_STRING msg) { //tlmp_warning ("send :%s:",msg.ptr); bo_websocket_send(con,1,msg); } // Write directly to the socket, no websocket encoding. // This is used when we proxy a websocket. We simply copy the data back and forth void HANDLE_WSS::write (const void *buf, size_t size) { con.write (buf,size); } /* Add a message to send at connection time. (When the state change to running) */ void HANDLE_WSS::addinitmsg(PARAM_STRING msg) { initmsgs.push_back(msg.ptr); } void HANDLE_WSS::setsession(PARAM_STRING sessionid) { session = sessionid.ptr; } // This grab the connection info (see the move()) // CONNECT_HTTP_INFO can't be copied void HANDLE_WSS::moveconnectinfo(CONNECT_HTTP_INFO &hcon) { con = move(hcon); } HANDLE_WSS::~HANDLE_WSS() { if (session.size() > 0){ //tlmp_warning ("HANDLE_WSS logout\n"); // We were running in websocket mode, so the current connection can't be reuse to talk to webapi. con.close(); (con,session); //printf ("Internal error=%d\n",internal_error); } }