diff --git a/README.md b/README.md new file mode 100644 index 0000000..225125a --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ + +# FLO_Twitter +This is a peer-peer Twitter like application totally encrypted in transit without needing a central server. +Current web based technologies have a inbound address problem namely ordinary web users do not have a fixed IP, so it is not easy to establish connection to them. +We propose to solve that problem by using TOR addresses for ordinary users which can provide a fixed inbound routable address on Internet for everyone including for those on dynamic IPs. +The TOR Project very easy to connect to a TOR based service. We believe it can form a stable architecture for peer to peer services. +Another limitation of current web based technologies is ordinary users cannot allocate a fixed port using just their web browsers for inbound connections. So every user will need to run their own webservers on which they can receive chat messages. We could not find a way to eliminate webservers. But we have found a very simple webserver called Mongoose Webserver, where a user can invoke a fixed port based service on click of a single button. +To facilitate globally unique identification of every peer, we propose to use FLO Blockchain IDs. Then user can then attach his TOR address onto his FLO id inside the FLO Blockchain. +Since the blockchain data is immutable, it will provide a continous uniterruptable source of connection information based on user's FLO ID. + +## Requirements +1. Ubuntu OS or its derivatives (or) Andoid +2. Onion Browser ([Brave]([https://brave.com/](https://brave.com/)) for Desktop or [Tor Browser](https://play.google.com/store/apps/details?id=org.torproject.torbrowser)/[Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) for Android) +3. FLO ID + +## Installation +The FLO_Twitter WSS can either run on Ubuntu OS or Android + +#### Ubuntu +1. Download or Clone this repo + + git clone https://github.com/ranchimall/FLO_Twitter +2. Open terminal in the directory + + cd FLO_Twitter +3. Run `autogen.sh` to install tor, configure tor and create the start file. + + sudo ./autogen.sh +#### Android +1. Download and extract [FLO_Twitter](https://github.com/ranchimall/FLO_Twitter/archive/master.zip) to Internal Storage Home (`/storage/emulated/0/`) **Make sure it is extracted as FLO_Twitter** (if its extracted as FLO_Twitter-master.. rename it to FLO_Twitter). The extracted path should be `/storage/emulated/0/FLO_Twitter/` +2. Install the app using `util/FLO_Twitter.apk` +3. Install [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) +4. Open Orbot + * Open Options (`:`) -> Hidden Services -> Hosted Services + * Add New Service with local port and onion port as `3232` and save it + * Enable `VPN Mode` and open :gear: + * add `FLO_Twitter` and the browser ([Orfox](https://play.google.com/store/apps/details?id=info.guardianproject.orfox) ll be enabled by default) + + +## Usage +The FLO_Twitter WSS should be started and then you can access it from any device. +### Starting FLO_Twitter WSS +#### Ubuntu +1. run start in terminal (in the `FLO_Twitter` directory). Enter a strong `server-password` which will be used to connect in client. + + ./start +2. `FLO_Twitter WSS` and `Tor` will be started automatically. (Hidden_service will be created on the 1st time). Your hidden_service `onion address` will be displayed in information dialog box +#### Android +1. Open `FLO_Twitter` app +2. Open `Orbot` app and `Start` + * Open Options (`:`) -> Hidden Services -> Hosted Services and Copy the `onion address` + +**Bookmark the onion address (`.onion:3232`) in the browser** + +### Accessing the client webpages +1. Open the `onion address` (`.onion:3232`) in Onion browser (`Brave` for desktop or `Tor Browser`/`Orbot`-enabled browsers for android) [not necessary to be on the same device] +3. Enter the `server-password` and click `Connect`. Access will be granted when entered correct server-password +4. Enter the `Username` and `FLO_ID privKey`. (FLO_ID privKey can be generated using [flo_webWallet](https://flo-webwallet.duckdns.org/) or [flo-core wallet](https://github.com/ranchimall/FLO-wallet-core)). **FLO_ID private key is important! DO NOT lose or share it!** +5. Click `SignIn`. New users and users changing the onion address or username will require to register in the FLO blockchain. (The registration is automatic, just click on ok when prompted). A minimum amount will be required to register [Balance recharge can be done using https://international.bittrex.com/]. Upon successful registration the txid will be alerted +* **Now your are logged on to FLO_Twitter.** +### FLO_Twitter Features +* Home + * Enter Tweet and post. + * Watch posted tweeted by users you follow +* Profile + * Click on the profile list in right-side column to navigate to the respective user profiles + * Watch tweets posted by an user + * Follow or Unfollow the user +* Message + * Click on the profile list to open the user chat + * Send and Receive messages with the other users + +**NOTE:** + * All Tweets are Signed using the Tweeter's FLO_ID PrivKey + * Hence all Tweets are verified by the Tweeter's PubKey + * All Messages are signed by the Sender's Privkey and Encrypted with Reciver's PubKey + * Only the Receiver can decrypt the message using their own PrivKey + * Message's signature is verified using Sender's PubKey diff --git a/app/web/app.js b/app/web/app.js index a33fdd5..8efa7f2 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -316,7 +316,7 @@ function ajax(method, uri){ } function reloadInitData(){ - refreshAPIdata.then(result => { + refreshAPIdata().then(result => { console.log(result); sessionStorage.profiles = JSON.stringify(profiles); sessionStorage.superNodeList = JSON.stringify(Array.from(superNodeList)); @@ -1310,4 +1310,4 @@ kBucketObj = { } }) } -} \ No newline at end of file +} diff --git a/app/web/login.js b/app/web/login.js index 3faf2eb..a7ab28b 100644 --- a/app/web/login.js +++ b/app/web/login.js @@ -84,7 +84,7 @@ function signIn(){ login(); }catch(msg){ console.log(msg) - customAlert(`${msg}`,'warning'); + customAlert(`${msg}`,'warning'); } } @@ -97,10 +97,12 @@ function login(){ } function signUp(){ - registerID(floID,window.location.host,privKey,privKey.getPubKeyHex(),username).then(result =>{ + registerID(floID,window.location.host,privKey,encrypt.getPubKeyHex(privKey),username).then(result =>{ customAlert(`Registration Successful!
txid : ${result}`,'success'); refreshAPIdata().then(result => { console.log(result); + sessionStorage.profiles = JSON.stringify(profiles); + sessionStorage.superNodeList = JSON.stringify(Array.from(superNodeList)); login(); }).catch(error => { console.log(error); diff --git a/util/FLO_Twitter.apk b/util/FLO_Twitter.apk new file mode 100644 index 0000000..5168c51 Binary files /dev/null and b/util/FLO_Twitter.apk differ diff --git a/util/floTwitterWSS_Android.c b/util/floTwitterWSS_Android.c new file mode 100644 index 0000000..8275e06 --- /dev/null +++ b/util/floTwitterWSS_Android.c @@ -0,0 +1,316 @@ +#include "mongoose.h" +static sig_atomic_t s_signal_received = 0; +static const char *s_http_port = "3232"; +static struct mg_serve_http_opts s_http_server_opts; +static char serverpass[100]; +static struct mg_connection *selfClient = NULL; + +struct tweetData{ + int id; + char data[5000]; +}; + +struct followData{ + char floID[35]; + char sign[150]; +}; + +static void signal_handler(int sig_num) { + signal(sig_num, signal_handler); // Reinstantiate signal handler + s_signal_received = sig_num; +} + +static int is_websocket(const struct mg_connection *nc) { + return nc->flags & MG_F_IS_WEBSOCKET; +} + +static void broadcast(struct mg_connection *nc, const struct mg_str msg) { + struct mg_connection *c; + char buf[5000]; + + snprintf(buf, sizeof(buf), "%.*s", (int) msg.len, msg.p); + printf("%s\n", buf); /* Local echo. */ + for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { + if (c == nc) continue; /* Don't send to the sender. */ + mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + } +} + +static void unicast(struct mg_connection *nc,const struct mg_str msg) { + char buf[5000]; + + snprintf(buf, sizeof(buf), "%.*s", (int) msg.len, msg.p); + printf("%s\n", buf); /* Local echo. */ + if(nc != NULL) + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + else + printf("No selfClient is connected!\n"); + +} + +static void storeTweet(struct mg_connection *nc,const struct mg_str msg){ + struct tweetData tweet; + snprintf(tweet.data, sizeof(tweet.data), "%.*s", (int) msg.len, msg.p); + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/tweet.bin","ab"); + if(fptr == NULL){ + printf("Error in opening tweet.bin\n"); + return; + } + fseek(fptr,0,SEEK_END); + long int filesize = ftell(fptr); + tweet.id = filesize/sizeof(tweet) + 1; + fwrite(&tweet,sizeof(tweet),1,fptr); + fclose(fptr); + //broadcast + struct mg_connection *c; + char buf[5050]; + snprintf(buf, sizeof(buf), "{\"id\":%d,\"data\":%s}", tweet.id, tweet.data); + printf("%s\n", buf); /* Local echo. */ + for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { + if (c == nc) continue; /* Don't send to the sender. */ + mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + } +} + +static void sendTweets(struct mg_connection *nc, const struct mg_str d){ + int i, n = 0; + for(i=1; i < d.len; i++) + n = (n*10) + (d.p[i] - '0'); + //printf("%d\n",n); + struct tweetData tweet; + char buf[5050]; + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/tweet.bin","rb"); + if(fptr == NULL){ + printf("Error in opening tweet.bin\n"); + return; + } + fseek(fptr, n*sizeof(tweet), SEEK_SET); + while(fread(&tweet,sizeof(tweet),1,fptr)){ + snprintf(buf, sizeof(buf), "{\"id\":%d,\"data\":%s}", tweet.id, tweet.data); + printf("%s\n", buf); + mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + } + fclose(fptr); +} + +static void Follower(const struct mg_str d){ + struct followData data; + snprintf(data.floID, sizeof(data.floID), "%.*s", (int) (34), &d.p[1]); + snprintf(data.sign, sizeof(data.sign), "%.*s", (int) (d.len-36), &d.p[36]); + printf("Follower : %s\n",data.floID); + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/followers.bin","ab"); + if(fptr == NULL){ + printf("Error in opening followers.bin\n"); + return; + } + fseek(fptr,0,SEEK_END); + fwrite(&data,sizeof(data),1,fptr); + fclose(fptr); +} + +static void Unfollower(const struct mg_str d){ + char floID[35]; + struct followData data; + snprintf(floID, sizeof(floID), "%.*s", (int) (34), &d.p[1]); + printf("Unfollower : %s\n",floID); + FILE *fp; + FILE *fp_tmp; + fp = fopen("/storage/emulated/0/FLO_Twitter/followers.bin", "rb"); + if (!fp) { + printf("Error in opening followers.bin\n"); + return; + } + fp_tmp = fopen("/storage/emulated/0/FLO_Twitter/tmp.bin", "wb"); + if (!fp) { + printf("Error in opening tmp.bin\n"); + return; + } + while(fread(&data,sizeof(data),1,fp)){ + if(strcmp(data.floID,floID)) //floID != follower.floID + fwrite(&data,sizeof(data),1,fp_tmp); + } + fclose(fp); + fclose(fp_tmp); + remove("/storage/emulated/0/FLO_Twitter/followers.bin"); + rename("/storage/emulated/0/FLO_Twitter/tmp.bin", "followers.bin"); + return; +} + +static void follow(const struct mg_str d){ + struct followData data; + snprintf(data.floID, sizeof(data.floID), "%.*s", (int) (34), &d.p[1]); + snprintf(data.sign, sizeof(data.sign), "%.*s", (int) (d.len-36), &d.p[36]); + printf("follow : %s\n",data.floID); + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/following.bin","ab"); + if(fptr == NULL){ + printf("Error in opening following.bin\n"); + return; + } + fseek(fptr,0,SEEK_END); + fwrite(&data,sizeof(data),1,fptr); + fclose(fptr); +} + +static void unfollow(const struct mg_str d){ + char floID[35]; + struct followData data; + snprintf(floID, sizeof(floID), "%.*s", (int) (34), &d.p[1]); + printf("unfollow : %s\n",floID); + FILE *fp; + FILE *fp_tmp; + fp = fopen("/storage/emulated/0/FLO_Twitter/following.bin", "rb"); + if (!fp) { + printf("Error in opening following.bin\n"); + return; + } + fp_tmp = fopen("/storage/emulated/0/FLO_Twitter/tmp.bin", "wb"); + if (!fp) { + printf("Error in opening tmp.bin\n"); + return; + } + while(fread(&data,sizeof(data),1,fp)){ + if(strcmp(data.floID,floID)) //floID != follower.floID + fwrite(&data,sizeof(data),1,fp_tmp); + } + fclose(fp); + fclose(fp_tmp); + remove("/storage/emulated/0/FLO_Twitter/following.bin"); + rename("/storage/emulated/0/FLO_Twitter/tmp.bin", "following.bin"); + return; +} + +static void storeIncoming(const struct mg_str msg){ + char buf[5000]; + snprintf(buf, sizeof(buf), "%.*s", (int) msg.len, msg.p); + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/incoming.bin","ab"); + if(fptr == NULL){ + printf("Error in opening incoming.bin\n"); + return; + } + fseek(fptr,0,SEEK_END); + fwrite(&buf,sizeof(buf),1,fptr); + fclose(fptr); +} + +static void forwardIncomings(){ + char buf[5000]; + FILE *fptr; + fptr = fopen("/storage/emulated/0/FLO_Twitter/incoming.bin","rb"); + if(fptr == NULL){ + printf("No new Incomings\n"); + return; + } + while(fread(&buf,sizeof(buf),1,fptr)){ + mg_send_websocket_frame(selfClient, WEBSOCKET_OP_TEXT, buf, strlen(buf)); + } + fclose(fptr); + remove("/storage/emulated/0/FLO_Twitter/incoming.bin"); +} +static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { + switch (ev) { + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + /* New websocket connection. Tell everybody. */ + //broadcast(nc, mg_mk_str("++ joined")); + break; + } + case MG_EV_WEBSOCKET_FRAME: { + struct websocket_message *wm = (struct websocket_message *) ev_data; + /* New websocket message. Tell everybody. */ + struct mg_str d = {(char *) wm->data, wm->size}; + if(selfClient == nc){ + if(d.p[0] == 'F') + Follower(d); + else if(d.p[0] == 'U') + Unfollower(d); + else if(d.p[0] == 'f') + follow(d); + else if(d.p[0] == 'u') + unfollow(d); + else + storeTweet(nc, d); + }else{ + if (d.p[0] == '$'){ + char pass[100]; + snprintf(pass, sizeof(pass), "%.*s",(int)d.len-1, &d.p[1]); + if(!strcmp(pass,serverpass)){ + if(selfClient!=NULL) + unicast(selfClient,mg_mk_str("$Another login is encountered! Please close/refresh this window")); + selfClient = nc; + unicast(selfClient,mg_mk_str("$Access Granted!")); + forwardIncomings(); + }else + unicast(nc,mg_mk_str("$Access Denied!")); + } + else if(d.p[0] == '#'){ + if(selfClient == NULL) + unicast(nc,mg_mk_str("#-")); + else + unicast(nc,mg_mk_str("#+")); + } + else if(d.p[0] == '>'){ + sendTweets(nc, d); + } + else { + if(selfClient==NULL) + storeIncoming(d); + else + unicast(selfClient,d); + } + } + + break; + } + case MG_EV_HTTP_REQUEST: { + mg_serve_http(nc, (struct http_message *) ev_data, s_http_server_opts); + break; + } + case MG_EV_CLOSE: { + /* Disconnect. Tell everybody. */ + if (is_websocket(nc)) { + if(nc == selfClient){ + selfClient = NULL; + broadcast(nc, mg_mk_str("#-")); + } + } + break; + } + } +} + +int main(int argc, char** argv) { + + if(argc<=1){ + printf("Enter server password : "); + scanf("%s",serverpass); + } + else + strcpy(serverpass,argv[1]); + + struct mg_mgr mgr; + struct mg_connection *nc; + + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + + mg_mgr_init(&mgr, NULL); + + nc = mg_bind(&mgr, s_http_port, ev_handler); + mg_set_protocol_http_websocket(nc); + s_http_server_opts.document_root = "/storage/emulated/0/FLO_Twitter/app/web/"; // Serve current directory + s_http_server_opts.enable_directory_listing = "no"; + + printf("Started on port %s\n", s_http_port); + while (s_signal_received == 0) { + mg_mgr_poll(&mgr, 200); + } + mg_mgr_free(&mgr); + + return 0; +} diff --git a/util/websocket_chat.c b/util/floTwitterWSS_linux.c similarity index 100% rename from util/websocket_chat.c rename to util/floTwitterWSS_linux.c