update
[umurmur.git] / shm_utils / umurmurd-websocket / src / uMurmurd_Websocket.c
1 /*
2  * uMurmurd Websocket server - HTTP/JSON serverexample
3  *
4  * Copyright (C) 2014 Michael P. Pounders <>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation:
9  *  version 2.1 of the License.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA  02110-1301  USA
20  */
21 #ifdef CMAKE_BUILD
22 #include "lws_config.h"
23 #endif
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <getopt.h>
29 #include <string.h>
30 #include <sys/time.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <assert.h>
34
35 #include <syslog.h>
36
37 #include <signal.h>
38
39 #include <jansson.h>
40
41 #include <libwebsockets.h>
42 #include "../../../src/sharedmemory.h"
43
44 int max_poll_elements;
45
46 struct pollfd *pollfds;
47 int *fd_lookup;
48 int count_pollfds;
49 int force_exit = 0;
50
51 enum demo_protocols {
52         /* always first */
53         PROTOCOL_HTTP = 0,
54
55         PROTOCOL_JSON_UMURMURD,
56
57         /* always last */
58         DEMO_PROTOCOL_COUNT
59 };
60
61
62
63 char *resource_path = "../web";
64
65 /*
66  * We take a strict whitelist approach to stop ../ attacks
67  */
68
69 struct serveable {
70         const char *urlpath;
71         const char *mimetype;
72 }; 
73
74 static const struct serveable whitelist[] = {
75         { "/favicon.ico", "image/x-icon" },
76   { "/css/mon_umurmurd.css", "text/css" },
77   { "/css/json.human.css", "text/css" },
78   { "/js/crel.js", "text/javascript" },
79   { "/js/json.human.js", "text/javascript" },
80   { "/js/jquery.min.js", "text/javascript" },
81   
82         /* last one is the default served if no match */
83         { "/mon_umurmurd.html", "text/html" },
84 };
85
86 struct per_session_data__http {
87         int fd;
88 };
89
90 /* this protocol server (always the first one) just knows how to do HTTP */
91
92 static int callback_http( struct libwebsocket_context *context,
93                                       struct libwebsocket *wsi,
94                                       enum libwebsocket_callback_reasons reason, void *user,
95                                                                     void *in, size_t len)
96 {
97 #if 0
98         char client_name[128];
99         char client_ip[128];
100 #endif
101         char buf[256];
102         char leaf_path[1024];
103         int n, m;
104         unsigned char *p;
105         static unsigned char buffer[4096];
106         struct stat stat_buf;
107         struct per_session_data__http *pss =
108                         (struct per_session_data__http *)user;
109
110         switch (reason) {
111         case LWS_CALLBACK_HTTP:
112
113                 /* check for the "send a big file by hand" example case */
114
115                 if (!strcmp((const char *)in, "/leaf.jpg")) {
116                         if (strlen(resource_path) > sizeof(leaf_path) - 10)
117                                 return -1;
118                         sprintf(leaf_path, "%s/leaf.jpg", resource_path);
119
120                         /* well, let's demonstrate how to send the hard way */
121
122                         p = buffer;
123
124                         pss->fd = open(leaf_path, O_RDONLY);
125
126                         if (pss->fd < 0)
127                                 return -1;
128
129                         fstat(pss->fd, &stat_buf);
130
131                         /*
132                          * we will send a big jpeg file, but it could be
133                          * anything.  Set the Content-Type: appropriately
134                          * so the browser knows what to do with it.
135                          */
136
137                         p += sprintf((char *)p,
138                                 "HTTP/1.0 200 OK\x0d\x0a"
139                                 "Server: libwebsockets\x0d\x0a"
140                                 "Content-Type: image/jpeg\x0d\x0a"
141                                         "Content-Length: %u\x0d\x0a\x0d\x0a",
142                                         (unsigned int)stat_buf.st_size);
143
144                         /*
145                          * send the http headers...
146                          * this won't block since it's the first payload sent
147                          * on the connection since it was established
148                          * (too small for partial)
149                          */
150
151                         n = libwebsocket_write(wsi, buffer,
152                                    p - buffer, LWS_WRITE_HTTP);
153
154                         if (n < 0) {
155                                 close(pss->fd);
156                                 return -1;
157                         }
158                         /*
159                          * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
160                          */
161                         libwebsocket_callback_on_writable(context, wsi);
162                         break;
163                 }
164
165                 /* if not, send a file the easy way */
166
167                 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
168                         if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
169                                 break;
170
171                 sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
172
173                 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
174                         return -1; /* through completion or error, close the socket */
175
176                 /*
177                  * notice that the sending of the file completes asynchronously,
178                  * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
179                  * it's done
180                  */
181
182                 break;
183
184         case LWS_CALLBACK_HTTP_FILE_COMPLETION:
185 //              lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
186                 /* kill the connection after we sent one file */
187                 return -1;
188
189         case LWS_CALLBACK_HTTP_WRITEABLE:
190                 /*
191                  * we can send more of whatever it is we were sending
192                  */
193
194                 do {
195                         n = read(pss->fd, buffer, sizeof buffer);
196                         /* problem reading, close conn */
197                         if (n < 0)
198                                 goto bail;
199                         /* sent it all, close conn */
200                         if (n == 0)
201                                 goto bail;
202                         /*
203                          * because it's HTTP and not websocket, don't need to take
204                          * care about pre and postamble
205                          */
206                         m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP);
207                         if (m < 0)
208                                 /* write failed, close conn */
209                                 goto bail;
210                         if (m != n)
211                                 /* partial write, adjust */
212                                 lseek(pss->fd, m - n, SEEK_CUR);
213
214                 } while (!lws_send_pipe_choked(wsi));
215                 libwebsocket_callback_on_writable(context, wsi);
216                 break;
217
218 bail:
219                 close(pss->fd);
220                 return -1;
221
222         /*
223          * callback for confirming to continue with client IP appear in
224          * protocol 0 callback since no websocket protocol has been agreed
225          * yet.  You can just ignore this if you won't filter on client IP
226          * since the default uhandled callback return is 0 meaning let the
227          * connection continue.
228          */
229
230         case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
231 #if 0
232                 libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name,
233                              sizeof(client_name), client_ip, sizeof(client_ip));
234
235                 fprintf(stderr, "Received network connect from %s (%s)\n",
236                                                         client_name, client_ip);
237 #endif
238                 /* if we returned non-zero from here, we kill the connection */
239                 break;
240
241         default:
242                 break;
243         }
244
245         return 0;
246 }
247
248 /*
249  * this is just an example of parsing handshake headers, you don't need this
250  * in your code unless you will filter allowing connections by the header
251  * content
252  */
253
254 static void
255 dump_handshake_info(struct libwebsocket *wsi)
256 {
257         int n;
258         static const char *token_names[WSI_TOKEN_COUNT] = {
259                 /*[WSI_TOKEN_GET_URI]       =*/ "GET URI",
260                 /*[WSI_TOKEN_HOST]                    =*/ "Host",
261                 /*[WSI_TOKEN_CONNECTION]          =*/ "Connection",
262                 /*[WSI_TOKEN_KEY1]                    =*/ "key 1",
263                 /*[WSI_TOKEN_KEY2]                    =*/ "key 2",
264                 /*[WSI_TOKEN_PROTOCOL]            =*/ "Protocol",
265                 /*[WSI_TOKEN_UPGRADE]               =*/ "Upgrade",
266                 /*[WSI_TOKEN_ORIGIN]                =*/ "Origin",
267                 /*[WSI_TOKEN_DRAFT]                   =*/ "Draft",
268                 /*[WSI_TOKEN_CHALLENGE]           =*/ "Challenge",
269
270                 /* new for 04 */
271                 /*[WSI_TOKEN_KEY]                       =*/ "Key",
272                 /*[WSI_TOKEN_VERSION]               =*/ "Version",
273                 /*[WSI_TOKEN_SWORIGIN]            =*/ "Sworigin",
274
275                 /* new for 05 */
276                 /*[WSI_TOKEN_EXTENSIONS]          =*/ "Extensions",
277
278                 /* client receives these */
279                 /*[WSI_TOKEN_ACCEPT]                =*/ "Accept",
280                 /*[WSI_TOKEN_NONCE]                   =*/ "Nonce",
281                 /*[WSI_TOKEN_HTTP]                    =*/ "Http",
282                 /*[WSI_TOKEN_MUXURL]          =*/ "MuxURL",
283         };
284         char buf[256];
285
286         for (n = 0; n < WSI_TOKEN_COUNT; n++) {
287                 if (!lws_hdr_total_length(wsi, n))
288                         continue;
289
290                 lws_hdr_copy(wsi, buf, sizeof buf, n);
291
292                 fprintf(stderr, "    %s = %s\n", token_names[n], buf);
293         }
294 }
295
296 void *getJsonData( unsigned char * buf, int *n )
297 {
298
299 int cc;
300 json_t *jarr1;
301 char *result = NULL;
302
303 json_t *root = NULL, *server = NULL, *client, *clients;
304
305     root = json_object();
306     clients = json_object();
307
308           
309           server = json_pack( "{s:{:s:i,s:i}}", 
310                               "server", 
311                               "clients_max", shmptr->server_max_clients, 
312                               "clients_connected", shmptr->clientcount );
313     
314         json_object_update( root, server );
315           
316         if( shmptr->clientcount )
317         {  
318           jarr1 = json_array();
319               
320           for( cc = 0 ; cc < shmptr->server_max_clients ; cc++ )
321           {
322           
323           if( !shmptr->client[cc].authenticated )
324             continue;
325                                                                                
326           client = json_pack( "{:s:s,s:s,s:i,s:s,s:I,s:I,s:I}", "username", 
327                                             shmptr->client[cc].username, 
328                                             "ipaddress", 
329                                             shmptr->client[cc].ipaddress,
330                                             "udp_port",
331                                             shmptr->client[cc].udp_port,
332                                             "channel",
333                                             shmptr->client[cc].channel,
334                                             "lastactivity",
335                                             shmptr->client[cc].lastActivity,
336                                             "connecttime",
337                                             shmptr->client[cc].connectTime,
338                                             "idleTime",
339                                             (long long unsigned int)shmptr->client[cc].lastActivity - shmptr->client[cc].idleTime                                            
340                                             );                                                                                                                                                   
341           json_array_append_new( jarr1, client );           
342           } 
343  json_object_set_new( clients, "clients", jarr1 );         
344  json_object_update( root, clients );
345 }
346   json_dump_file(root, "json.txt", JSON_PRESERVE_ORDER | JSON_INDENT(4) );        
347   result = json_dumps(root, JSON_PRESERVE_ORDER | JSON_COMPACT );
348
349   *n = sprintf( (char *)&buf[LWS_SEND_BUFFER_PRE_PADDING], "%s", result  );
350      
351
352   if( result )
353     free( result );
354
355   json_decref(root);
356   return 0;         
357 }
358
359 struct per_session_data__umurmur_json {
360         int test;
361 };
362
363 static int
364 callback_umurmur_json( struct libwebsocket_context *context,
365                                          struct libwebsocket *wsi,
366                                          enum libwebsocket_callback_reasons reason,
367                                                      void *user, void *in, size_t len)
368 {
369         int m, n;
370
371         unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096 +
372                                                         LWS_SEND_BUFFER_POST_PADDING];
373         
374         //struct per_session_data__umurmur_json *pss = (struct per_session_data__umurmur_json *)user;
375
376         switch (reason) {
377
378         case LWS_CALLBACK_ESTABLISHED:
379                 lwsl_info("callback_umurmur_json: LWS_CALLBACK_ESTABLISHED\n");            
380                 break;
381
382         case LWS_CALLBACK_SERVER_WRITEABLE:
383     getJsonData( buf, &n );
384     m = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], n, LWS_WRITE_TEXT);  //printf("N: %d M: %d\n", n, m );
385
386     if( m == n )
387         return 1;
388                 break;
389
390         case LWS_CALLBACK_RECEIVE:
391         //fprintf(stderr, "rx %d\n", (int)len);
392                 if (len < 6)
393                         break;
394                 if (strcmp((const char *)in, "update\n") == 0)
395                         libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol( wsi ));
396       
397                 break;
398         /*
399          * this just demonstrates how to use the protocol filter. If you won't
400          * study and reject connections based on header content, you don't need
401          * to handle this callback
402          */
403
404         case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
405                 dump_handshake_info(wsi);
406                 /* you could return non-zero here and kill the connection */
407                 break;
408
409         default:
410                 break;
411         }
412
413         return 0;
414 }
415
416
417
418 /* list of supported protocols and callbacks */
419
420 static struct libwebsocket_protocols protocols[] = {
421         /* first protocol must always be HTTP handler */
422
423         {
424                 "http-only",                            /* name */
425                 callback_http,                                /* callback */
426                 sizeof (struct per_session_data__http), /* per_session_data_size */
427                 0,                                                      /* max frame size / rx buffer */
428         },
429         {
430                 "umurmur-json-protocol",
431                 callback_umurmur_json,
432                 sizeof(struct per_session_data__umurmur_json),
433                 128,
434         },
435         { NULL, NULL, 0, 0 } /* terminator */
436 };
437
438 void sighandler(int sig)
439 {
440         force_exit = 1;
441 }
442
443 static struct option options[] = {
444         { "help",       no_argument,            NULL, 'h' },
445         { "debug",      required_argument,      NULL, 'd' },
446         { "port",       required_argument,      NULL, 'p' },
447         { "ssl",        no_argument,            NULL, 's' },
448         { "interface",  required_argument,      NULL, 'i' },
449         { "daemonize",  no_argument,            NULL, 'D' },
450         { "resource_path", required_argument,           NULL, 'r' },
451         { NULL, 0, 0, 0 }
452 };
453
454 int main(int argc, char **argv)
455 {
456         char cert_path[1024];
457         char key_path[1024];
458         int n = 0;
459         int use_ssl = 0;
460         struct libwebsocket_context *context;
461         int opts = 0;
462         char interface_name[128] = "";
463         const char *iface = NULL;
464 //      unsigned int oldus = 0;
465         struct lws_context_creation_info info;
466
467   int syslog_options = LOG_PID | LOG_PERROR; 
468
469         int debug_level = 7;
470
471         int daemonize = 0;
472
473
474         memset(&info, 0, sizeof info);
475         info.port = 7681;
476
477         while (n >= 0) {
478                 n = getopt_long(argc, argv, "i:hsp:d:Dr:", options, NULL);
479                 if (n < 0)
480                         continue;
481                 switch (n) {
482                 case 'D':
483                         daemonize = 1;
484                         syslog_options &= ~LOG_PERROR;
485                         break;
486
487                 case 'd':
488                         debug_level = atoi(optarg);
489                         break;
490                 case 's':
491                         use_ssl = 1;
492                         break;
493                 case 'p':
494                         info.port = atoi(optarg);
495                         break;
496                 case 'i':
497                         strncpy(interface_name, optarg, sizeof interface_name);
498                         interface_name[(sizeof interface_name) - 1] = '\0';
499                         iface = interface_name;
500                         break;
501                 case 'r':
502                         resource_path = optarg;
503                         printf("Setting resource path to \"%s\"\n", resource_path);
504                         break;
505                 case 'h':
506                         fprintf(stderr, "Usage: test-server "
507                                         "[--port=<p>] [--ssl] "
508                                         "[-d <log bitfield>] "
509                                         "[--resource_path <path>]\n");
510                         exit(1);
511                 }
512         }
513
514 key_t key = 0x53021d79;
515
516                     if( ( shmid = shmget( key, 0, 0) ) == -1 )
517                     {
518                         perror("shmget");
519                         printf( "umurmurd doesn't seem to be running\n\r" );                        
520                         exit(EXIT_FAILURE);
521                     }
522
523                     
524                     if( ( shmptr = shmat( shmid,0, 0 ) ) == (void *) -1 )   
525                     {
526                         perror("shmat");
527                         exit(EXIT_FAILURE);
528                     }
529
530
531         /* 
532          * normally lock path would be /var/lock/lwsts or similar, to
533          * simplify getting started without having to take care about
534          * permissions or running as root, set to /tmp/.lwsts-lock
535          */
536         if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
537                 fprintf(stderr, "Failed to daemonize\n");
538                 return 1;
539         }
540
541
542         signal(SIGINT, sighandler);
543
544
545         /* we will only try to log things according to our debug_level */
546         setlogmask(LOG_UPTO (LOG_DEBUG));
547         openlog("lwsts", syslog_options, LOG_DAEMON);
548
549
550         /* tell the library what debug level to emit and to send it to syslog */
551         lws_set_log_level(debug_level, lwsl_emit_syslog);
552
553         lwsl_notice("uMurmurd Websocket server - "
554                         "(C) Copyright 2014 Michael J. Pounders <> - "
555                                                     "licensed under LGPL2.1\n");
556
557         info.iface = iface;
558         info.protocols = protocols;
559
560         info.extensions = libwebsocket_get_internal_extensions();
561
562         if (!use_ssl) {
563                 info.ssl_cert_filepath = NULL;
564                 info.ssl_private_key_filepath = NULL;
565         } else {
566                 if (strlen(resource_path) > sizeof(cert_path) - 32) {
567                         lwsl_err("resource path too long\n");
568                         return -1;
569                 }
570                 sprintf(cert_path, "%s/ssl/umurmurd_websocket.pem",
571                                                                 resource_path);
572                 if (strlen(resource_path) > sizeof(key_path) - 32) {
573                         lwsl_err("resource path too long\n");
574                         return -1;
575                 }
576                 sprintf(key_path, "%s/ssl/umurmurd_websocket.key.pem",
577                                                                 resource_path);
578
579                 info.ssl_cert_filepath = cert_path;
580                 info.ssl_private_key_filepath = key_path;
581         }
582         info.gid = -1;
583         info.uid = -1;
584         info.options = opts;
585
586         context = libwebsocket_create_context(&info);
587         if (context == NULL) {
588                 lwsl_err("libwebsocket init failed\n");
589                 return -1;
590         }
591
592         n = 0;
593         while (n >= 0 && !force_exit) {
594                 struct timeval tv;
595
596                 gettimeofday(&tv, NULL);
597
598                 /*
599                  * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
600                  * live websocket connection using the DUMB_INCREMENT protocol,
601                  * as soon as it can take more packets (usually immediately)
602                  */
603
604 //              if (((unsigned int)tv.tv_usec - oldus) > 50000) {
605 //                      libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_JSON_UMURMURD]);
606 //       oldus = tv.tv_usec;
607 //              }
608
609
610                 /*
611                  * If libwebsockets sockets are all we care about,
612                  * you can use this api which takes care of the poll()
613                  * and looping through finding who needed service.
614                  *
615                  * If no socket needs service, it'll return anyway after
616                  * the number of ms in the second argument.
617                  */
618
619                 n = libwebsocket_service(context, 50);
620
621         }
622
623
624         libwebsocket_context_destroy(context);
625
626         lwsl_notice("umurmur_websocket server exited cleanly\n");
627
628   shmdt( shmptr );
629         closelog();
630
631         return 0;
632 }