blob: 13648baac5b14e76e46f9578e486c9bb86d4aa0d [file] [log] [blame]
Tomas Cejkad340dbf2013-03-24 20:36:57 +01001/*
2 * libwebsockets-test-server - libwebsockets test implementation
3 *
4 * Copyright (C) 2010-2011 Andy Green <andy@warmcat.com>
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#include <stdio.h>
22#include <stdlib.h>
23#include <unistd.h>
24#include <getopt.h>
25#include <string.h>
26#include <sys/time.h>
27#include <sys/stat.h>
Tomas Cejkaba21b382013-04-13 02:37:32 +020028#include <sys/queue.h>
Tomas Cejkad340dbf2013-03-24 20:36:57 +010029#include <fcntl.h>
30#include <assert.h>
Tomas Cejkaba21b382013-04-13 02:37:32 +020031#include <pthread.h>
32#include <libnetconf.h>
33#include <libwebsockets.h>
Tomas Cejkad340dbf2013-03-24 20:36:57 +010034#include "notification_module.h"
Tomas Cejkaba21b382013-04-13 02:37:32 +020035#include "mod_netconf.h"
Tomas Cejkad340dbf2013-03-24 20:36:57 +010036
37#ifndef TEST_NOTIFICATION_SERVER
38#include <httpd.h>
39#include <http_log.h>
Tomas Cejkaba21b382013-04-13 02:37:32 +020040#include <apr_hash.h>
41#include <apr_tables.h>
42
43#else
44static int force_exit = 0;
Tomas Cejkad340dbf2013-03-24 20:36:57 +010045#endif
46
47#if defined(TEST_NOTIFICATION_SERVER) || defined(WITH_NOTIFICATIONS)
48static int close_testing;
49static int max_poll_elements;
50
51static struct pollfd *pollfds;
52static int *fd_lookup;
53static int count_pollfds;
Tomas Cejkad340dbf2013-03-24 20:36:57 +010054static struct libwebsocket_context *context = NULL;
55static server_rec *http_server = NULL;
56
Tomas Cejkaba21b382013-04-13 02:37:32 +020057struct ntf_thread_config {
58 struct nc_session *session;
59 char *session_hash;
60};
61
62static apr_hash_t *netconf_locked_sessions = NULL;
63static pthread_key_t thread_key;
64
Tomas Cejkad340dbf2013-03-24 20:36:57 +010065/*
66 * This demo server shows how to use libwebsockets for one or more
67 * websocket protocols in the same server
68 *
69 * It defines the following websocket protocols:
70 *
71 * dumb-increment-protocol: once the socket is opened, an incrementing
72 * ascii string is sent down it every 50ms.
73 * If you send "reset\n" on the websocket, then
74 * the incrementing number is reset to 0.
75 *
76 * lws-mirror-protocol: copies any received packet to every connection also
77 * using this protocol, including the sender
78 */
79
80enum demo_protocols {
81 /* always first */
82 PROTOCOL_HTTP = 0,
83
84 PROTOCOL_NOTIFICATION,
85
86 /* always last */
87 DEMO_PROTOCOL_COUNT
88};
89
90
91#define LOCAL_RESOURCE_PATH "."
92char *resource_path = LOCAL_RESOURCE_PATH;
93
94/*
95 * We take a strict whitelist approach to stop ../ attacks
96 */
97
98struct serveable {
99 const char *urlpath;
100 const char *mimetype;
Tomas Cejka15c56302013-05-30 01:11:30 +0200101};
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100102
103static const struct serveable whitelist[] = {
104 { "/favicon.ico", "image/x-icon" },
105 { "/libwebsockets.org-logo.png", "image/png" },
106
107 /* last one is the default served if no match */
108 { "/test.html", "text/html" },
109};
110
111struct per_session_data__http {
112 int fd;
113};
114
115/* this protocol server (always the first one) just knows how to do HTTP */
116
117static int callback_http(struct libwebsocket_context *context,
118 struct libwebsocket *wsi,
119 enum libwebsocket_callback_reasons reason, void *user,
120 void *in, size_t len)
121{
122 char client_name[128];
123 char client_ip[128];
124 char buf[256];
125 int n, m;
126 unsigned char *p;
127 static unsigned char buffer[4096];
128 struct stat stat_buf;
129 struct per_session_data__http *pss = (struct per_session_data__http *)user;
130 int fd = (int)(long)in;
131
132 switch (reason) {
133 case LWS_CALLBACK_HTTP:
134
135 /* check for the "send a big file by hand" example case */
136
137 if (!strcmp((const char *)in, "/leaf.jpg")) {
138 char leaf_path[1024];
139 snprintf(leaf_path, sizeof(leaf_path), "%s/leaf.jpg", resource_path);
140
141 /* well, let's demonstrate how to send the hard way */
142
143 p = buffer;
144
145 pss->fd = open(leaf_path, O_RDONLY);
146
147 if (pss->fd < 0)
148 return -1;
149
150 fstat(pss->fd, &stat_buf);
151
152 /*
153 * we will send a big jpeg file, but it could be
154 * anything. Set the Content-Type: appropriately
155 * so the browser knows what to do with it.
156 */
157
158 p += sprintf((char *)p,
159 "HTTP/1.0 200 OK\x0d\x0a"
160 "Server: libwebsockets\x0d\x0a"
161 "Content-Type: image/jpeg\x0d\x0a"
162 "Content-Length: %u\x0d\x0a\x0d\x0a",
163 (unsigned int)stat_buf.st_size);
164
165 /*
166 * send the http headers...
167 * this won't block since it's the first payload sent
168 * on the connection since it was established
169 * (too small for partial)
170 */
171
172 n = libwebsocket_write(wsi, buffer,
173 p - buffer, LWS_WRITE_HTTP);
174
175 if (n < 0) {
176 close(pss->fd);
177 return -1;
178 }
179 /*
180 * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
181 */
182 libwebsocket_callback_on_writable(context, wsi);
183 break;
184 }
185
186 /* if not, send a file the easy way */
187
188 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
189 if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
190 break;
191
192 sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
193
194 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
195 return -1; /* through completion or error, close the socket */
196
197 /*
198 * notice that the sending of the file completes asynchronously,
199 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
200 * it's done
201 */
202
203 break;
204
205 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
206// lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
207 /* kill the connection after we sent one file */
208 return -1;
209
210 case LWS_CALLBACK_HTTP_WRITEABLE:
211 /*
212 * we can send more of whatever it is we were sending
213 */
214
215 do {
216 n = read(pss->fd, buffer, sizeof buffer);
217 /* problem reading, close conn */
218 if (n < 0)
219 goto bail;
220 /* sent it all, close conn */
221 if (n == 0)
222 goto bail;
223 /*
224 * because it's HTTP and not websocket, don't need to take
225 * care about pre and postamble
226 */
227 m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP);
228 if (m < 0)
229 /* write failed, close conn */
230 goto bail;
231 if (m != n)
232 /* partial write, adjust */
233 lseek(pss->fd, m - n, SEEK_CUR);
234
235 } while (!lws_send_pipe_choked(wsi));
236 libwebsocket_callback_on_writable(context, wsi);
237 break;
238
239bail:
240 close(pss->fd);
241 return -1;
242
243 /*
244 * callback for confirming to continue with client IP appear in
245 * protocol 0 callback since no websocket protocol has been agreed
246 * yet. You can just ignore this if you won't filter on client IP
247 * since the default uhandled callback return is 0 meaning let the
248 * connection continue.
249 */
250
251 case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
252 libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name,
253 sizeof(client_name), client_ip, sizeof(client_ip));
254
Tomas Cejkaba21b382013-04-13 02:37:32 +0200255 //fprintf(stderr, "Received network connect from %s (%s)\n", client_name, client_ip);
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100256 /* if we returned non-zero from here, we kill the connection */
257 break;
258
259 /*
260 * callbacks for managing the external poll() array appear in
261 * protocol 0 callback
262 */
263
264 case LWS_CALLBACK_ADD_POLL_FD:
265
266 if (count_pollfds >= max_poll_elements) {
267 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
268 return 1;
269 }
270
271 fd_lookup[fd] = count_pollfds;
272 pollfds[count_pollfds].fd = fd;
273 pollfds[count_pollfds].events = (int)(long)len;
274 pollfds[count_pollfds++].revents = 0;
275 break;
276
277 case LWS_CALLBACK_DEL_POLL_FD:
278 if (!--count_pollfds)
279 break;
280 m = fd_lookup[fd];
281 /* have the last guy take up the vacant slot */
282 pollfds[m] = pollfds[count_pollfds];
283 fd_lookup[pollfds[count_pollfds].fd] = m;
284 break;
285
286 case LWS_CALLBACK_SET_MODE_POLL_FD:
287 pollfds[fd_lookup[fd]].events |= (int)(long)len;
288 break;
289
290 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
291 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
292 break;
293
294 default:
295 break;
296 }
297
298 return 0;
299}
300
Tomas Cejkaba21b382013-04-13 02:37:32 +0200301/**
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100302 * this is just an example of parsing handshake headers, you don't need this
303 * in your code unless you will filter allowing connections by the header
304 * content
305 */
Tomas Cejkaba21b382013-04-13 02:37:32 +0200306//static void dump_handshake_info(struct libwebsocket *wsi)
307//{
308// int n;
309// static const char *token_names[WSI_TOKEN_COUNT] = {
310// /*[WSI_TOKEN_GET_URI] =*/ "GET URI",
311// /*[WSI_TOKEN_HOST] =*/ "Host",
312// /*[WSI_TOKEN_CONNECTION] =*/ "Connection",
313// /*[WSI_TOKEN_KEY1] =*/ "key 1",
314// /*[WSI_TOKEN_KEY2] =*/ "key 2",
315// /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol",
316// /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade",
317// /*[WSI_TOKEN_ORIGIN] =*/ "Origin",
318// /*[WSI_TOKEN_DRAFT] =*/ "Draft",
319// /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge",
320//
321// /* new for 04 */
322// /*[WSI_TOKEN_KEY] =*/ "Key",
323// /*[WSI_TOKEN_VERSION] =*/ "Version",
324// /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin",
325//
326// /* new for 05 */
327// /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions",
328//
329// /* client receives these */
330// /*[WSI_TOKEN_ACCEPT] =*/ "Accept",
331// /*[WSI_TOKEN_NONCE] =*/ "Nonce",
332// /*[WSI_TOKEN_HTTP] =*/ "Http",
333// /*[WSI_TOKEN_MUXURL] =*/ "MuxURL",
334// };
335// char buf[256];
336//
337// for (n = 0; n < WSI_TOKEN_COUNT; n++) {
338// if (!lws_hdr_total_length(wsi, n))
339// continue;
340//
341// //lws_hdr_copy(wsi, buf, sizeof buf, n);
342//
343// //fprintf(stderr, " %s = %s\n", token_names[n], buf);
344// }
345//}
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100346
347/* dumb_increment protocol */
348
349/*
350 * one of these is auto-created for each connection and a pointer to the
351 * appropriate instance is passed to the callback in the user parameter
352 *
353 * for this example protocol we use it to individualize the count for each
354 * connection.
355 */
356
Tomas Cejkaba21b382013-04-13 02:37:32 +0200357struct per_session_data__notif_client {
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100358 int number;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200359 char *session_key;
360 struct nc_session *session;
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100361};
362
Tomas Cejkaba21b382013-04-13 02:37:32 +0200363struct session_with_mutex *get_ncsession_from_key(const char *session_key)
364{
365 struct session_with_mutex *locked_session = NULL;
366 if (session_key == NULL) {
367 return (NULL);
368 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200369 locked_session = (struct session_with_mutex *)apr_hash_get(netconf_locked_sessions, session_key, APR_HASH_KEY_STRING);
Tomas Cejkaba21b382013-04-13 02:37:32 +0200370 return locked_session;
371}
372
373/* rpc parameter is freed after the function call */
374static int send_recv_process(struct nc_session *session, const char* operation, nc_rpc* rpc)
375{
376 nc_reply *reply = NULL;
377 char *data = NULL;
378 int ret = EXIT_SUCCESS;
379
380 /* send the request and get the reply */
381 switch (nc_session_send_recv(session, rpc, &reply)) {
382 case NC_MSG_UNKNOWN:
383 if (nc_session_get_status(session) != NC_SESSION_STATUS_WORKING) {
384 #ifndef TEST_NOTIFICATION_SERVER
385 if (http_server != NULL) {
386 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: receiving rpc-reply failed.");
387 }
388 #endif
389 //cmd_disconnect(NULL);
390 ret = EXIT_FAILURE;
391 break;
392 }
393 #ifndef TEST_NOTIFICATION_SERVER
394 if (http_server != NULL) {
395 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: Unknown error occurred.");
396 }
397 #endif
398 ret = EXIT_FAILURE;
399 break;
400 case NC_MSG_NONE:
401 /* error occurred, but processed by callback */
402 break;
403 case NC_MSG_REPLY:
404 switch (nc_reply_get_type(reply)) {
405 case NC_REPLY_OK:
406 break;
407 case NC_REPLY_DATA:
408 #ifndef TEST_NOTIFICATION_SERVER
409 if (http_server != NULL) {
410 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notifications: recv: %s.", data = nc_reply_get_data (reply));
411 free(data);
412 }
413 #endif
414 break;
415 case NC_REPLY_ERROR:
416 /* wtf, you shouldn't be here !?!? */
417 #ifndef TEST_NOTIFICATION_SERVER
418 if (http_server != NULL) {
419 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: operation failed, but rpc-error was not processed.");
420 }
421 #endif
422 ret = EXIT_FAILURE;
423 break;
424 default:
425 #ifndef TEST_NOTIFICATION_SERVER
426 if (http_server != NULL) {
427 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: unexpected operation result.");
428 }
429 #endif
430 ret = EXIT_FAILURE;
431 break;
432 }
433 break;
434 default:
435 #ifndef TEST_NOTIFICATION_SERVER
436 if (http_server != NULL) {
437 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: Unknown error occurred.");
438 }
439 #endif
440 ret = EXIT_FAILURE;
441 break;
442 }
443 nc_rpc_free(rpc);
444 nc_reply_free(reply);
445
446 return (ret);
447}
448
449/**
450 * \brief Callback to store incoming notification
451 * \param [in] eventtime - time when notification occured
452 * \param [in] content - content of notification
453 */
454static void notification_fileprint (time_t eventtime, const char* content)
455{
456 char t[128];
457 struct session_with_mutex *target_session = NULL;
458 notification_t *ntf = NULL;
459 char *session_hash = NULL;
460
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200461 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Accepted notif: %lu %s\n", (unsigned long int) eventtime, content);
Tomas Cejka15c56302013-05-30 01:11:30 +0200462
Tomas Cejkaba21b382013-04-13 02:37:32 +0200463 session_hash = pthread_getspecific(thread_key);
464 if (http_server != NULL) {
465 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: fileprint getspecific (%s)", session_hash);
466 }
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200467 if (pthread_rwlock_wrlock(&session_lock) != 0) {
468 #ifndef TEST_NOTIFICATION_SERVER
469 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
470 #endif
471 return;
472 }
473 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Get session with mutex from key %s.", session_hash);
Tomas Cejkaba21b382013-04-13 02:37:32 +0200474 target_session = get_ncsession_from_key(session_hash);
475 if (target_session == NULL) {
476 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "no session found last_session_key (%s)", session_hash);
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200477 if (pthread_rwlock_unlock (&session_lock) != 0) {
478#ifndef TEST_NOTIFICATION_SERVER
479 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
480#endif
481 return;
482 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200483 return;
484 }
Tomas Cejka15c56302013-05-30 01:11:30 +0200485
486 t[0] = 0;
487 strftime(t, sizeof(t), "%c", localtime(&eventtime));
488
Tomas Cejkaba21b382013-04-13 02:37:32 +0200489 if (pthread_mutex_lock(&target_session->lock) != 0) {
490 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
491 return;
492 }
493 if (target_session->notifications == NULL) {
494 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "target_session->notifications is NULL");
495 if (pthread_mutex_unlock(&target_session->lock) != 0) {
496 ap_log_error (APLOG_MARK, APLOG_ERR, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
497 return;
498 }
499 return;
500 }
501 if (http_server != NULL) {
502 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: ready to push to notifications queue");
503 }
504 ntf = (notification_t *) apr_array_push(target_session->notifications);
505 if (ntf == NULL) {
506 ap_log_error (APLOG_MARK, APLOG_ERR, 0, http_server, "Failed to allocate element ");
507 if (pthread_mutex_unlock(&target_session->lock) != 0) {
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200508 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
509 return;
510 }
511 if (pthread_rwlock_unlock (&session_lock) != 0) {
512#ifndef TEST_NOTIFICATION_SERVER
513 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
514#endif
Tomas Cejkaba21b382013-04-13 02:37:32 +0200515 return;
516 }
517 return;
518 }
Tomas Cejka73286932013-05-27 22:54:35 +0200519 ntf->eventtime = eventtime;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200520 ntf->content = strdup(content);
521
522 if (http_server != NULL) {
523 ap_log_error (APLOG_MARK, APLOG_NOTICE, 0, http_server, "added notif to queue %u (%s)", (unsigned int) ntf->eventtime, "notifikace");
524 }
525
526 if (pthread_mutex_unlock(&target_session->lock) != 0) {
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200527 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
528 if (pthread_rwlock_unlock (&session_lock) != 0) {
529 #ifndef TEST_NOTIFICATION_SERVER
530 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
531 #endif
532 return;
533 }
534 return;
535 }
536 if (pthread_rwlock_unlock (&session_lock) != 0) {
537 #ifndef TEST_NOTIFICATION_SERVER
538 ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
539 #endif
Tomas Cejkaba21b382013-04-13 02:37:32 +0200540 return;
541 }
542}
543
544/**
545 * \brief Thread for libnetconf notifications dispatch
546 * \param [in] arg - struct ntf_thread_config * with nc_session
547 */
548void* notification_thread(void* arg)
549{
550 struct ntf_thread_config *config = (struct ntf_thread_config*)arg;
551 #ifndef TEST_NOTIFICATION_SERVER
552 if (http_server != NULL) {
553 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "notifications: in thread for libnetconf notifications");
554 }
555 #endif
Tomas Cejka15c56302013-05-30 01:11:30 +0200556
557 /* store hash identification of netconf session for notifications printing callback */
558 if (pthread_setspecific(thread_key, config->session_hash) != 0) {
559 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: cannot set thread-specific hash value.");
Tomas Cejkaba21b382013-04-13 02:37:32 +0200560 }
Tomas Cejka15c56302013-05-30 01:11:30 +0200561
Tomas Cejkaba21b382013-04-13 02:37:32 +0200562 if (http_server != NULL) {
563 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notifications: dispatching");
564 }
565 ncntf_dispatch_receive(config->session, notification_fileprint);
566 #ifndef TEST_NOTIFICATION_SERVER
567 if (http_server != NULL) {
568 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "notifications: ended thread for libnetconf notifications");
569 }
570 #endif
571 free(config);
572 return (NULL);
573}
574
575
576int notif_subscribe(struct session_with_mutex *locked_session, const char *session_hash, time_t start_time, time_t stop_time)
577{
578 time_t start = -1;
579 time_t stop = -1;
580 struct nc_filter *filter = NULL;
581 char *stream = NULL;
582 nc_rpc *rpc = NULL;
583 pthread_t thread;
584 struct ntf_thread_config *tconfig;
585 struct nc_session *session;
586
587 if (locked_session == NULL) {
588 if (http_server != NULL) {
589 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: no locked_session was given.");
590 }
591 return (EXIT_FAILURE);
592 }
593
594 session = locked_session->session;
595
596 start = time(NULL) + start_time;
597 stop = time(NULL) + stop_time;
598 if (http_server != NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: history: %u %u", (unsigned int) start, (unsigned int) stop);
599 }
600
601 if (session == NULL) {
602 #ifndef TEST_NOTIFICATION_SERVER
603 if (http_server != NULL) {
604 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: NETCONF session not established.");
605 }
606 #endif
607 return (EXIT_FAILURE);
608 }
609
610 /* check if notifications are allowed on this session */
611 if (nc_session_notif_allowed(session) == 0) {
612 #ifndef TEST_NOTIFICATION_SERVER
613 if (http_server != NULL) {
614 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: Notification subscription is not allowed on this session.");
615 }
616 #endif
617 return (EXIT_FAILURE);
618 }
619 /* check times */
620 if (start != -1 && stop != -1 && start > stop) {
621 #ifndef TEST_NOTIFICATION_SERVER
622 if (http_server != NULL) {
623 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: Subscription start time must be lower than the end time.");
624 }
625 #endif
626 return (EXIT_FAILURE);
627 }
628
629 /* create requests */
630 rpc = nc_rpc_subscribe(stream, filter, (start_time == 0)?NULL:&start, (stop_time == 0)?NULL:&stop);
631 nc_filter_free(filter);
632 if (rpc == NULL) {
633 #ifndef TEST_NOTIFICATION_SERVER
634 if (http_server != NULL) {
635 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: creating an rpc request failed.");
636 }
637 #endif
638 return (EXIT_FAILURE);
639 }
640
641 if (send_recv_process(session, "subscribe", rpc) != 0) {
642 return (EXIT_FAILURE);
643 }
644 rpc = NULL; /* just note that rpc is already freed by send_recv_process() */
Tomas Cejka654f84e2013-04-19 11:55:01 +0200645 locked_session->ntfc_subscribed = 1;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200646
647 tconfig = malloc(sizeof(struct ntf_thread_config));
648 tconfig->session = session;
649 tconfig->session_hash = strdup(session_hash);
650 if (http_server != NULL) {
651 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: creating libnetconf notification thread (%s).",
652 tconfig->session_hash);
653 }
Tomas Cejka654f84e2013-04-19 11:55:01 +0200654
Tomas Cejkaba21b382013-04-13 02:37:32 +0200655 if (pthread_create(&thread, NULL, notification_thread, tconfig) != 0) {
656 #ifndef TEST_NOTIFICATION_SERVER
657 if (http_server != NULL) {
658 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: creating a thread for receiving notifications failed");
659 }
660 #endif
661 return (EXIT_FAILURE);
662 }
663 pthread_detach(thread);
664 return (EXIT_SUCCESS);
665}
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100666
667static int callback_notification(struct libwebsocket_context *context,
668 struct libwebsocket *wsi,
669 enum libwebsocket_callback_reasons reason,
Tomas Cejkaba21b382013-04-13 02:37:32 +0200670 void *user, void *in, size_t len)
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100671{
Tomas Cejkaba21b382013-04-13 02:37:32 +0200672 int n = 0;
673 int m = 0;
674 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096 + LWS_SEND_BUFFER_POST_PADDING];
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100675 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
Tomas Cejkaba21b382013-04-13 02:37:32 +0200676 struct per_session_data__notif_client *pss = (struct per_session_data__notif_client *)user;
677
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100678 switch (reason) {
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100679 case LWS_CALLBACK_ESTABLISHED:
Tomas Cejka15c56302013-05-30 01:11:30 +0200680 if (http_server != NULL) {
681 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification client connected.");
682 }
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100683 pss->number = 0;
684 break;
685
686 case LWS_CALLBACK_SERVER_WRITEABLE:
Tomas Cejkaba21b382013-04-13 02:37:32 +0200687 if (pss->session_key == NULL) {
688 return 0;
689 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200690
691 struct session_with_mutex *ls = get_ncsession_from_key(pss->session_key);
692 if (ls == NULL) {
693 if (http_server != NULL) {
694 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notification: session not found");
695 }
696 return -1;
697 }
698 if (pthread_mutex_lock(&ls->lock) != 0) {
699 #ifndef TEST_NOTIFICATION_SERVER
700 if (http_server != NULL) {
701 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notification: cannot lock session");
702 }
703 #endif
704 }
705 notification_t *notif = NULL;
706 if (ls->notifications == NULL) {
707 #ifndef TEST_NOTIFICATION_SERVER
708 if (http_server != NULL) {
709 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notification: no notifications array");
710 }
711 #endif
712 pthread_mutex_unlock(&ls->lock);
713 return -1;
714 }
Tomas Cejka654f84e2013-04-19 11:55:01 +0200715 if (!apr_is_empty_array(ls->notifications)) {
Tomas Cejkaba21b382013-04-13 02:37:32 +0200716
Tomas Cejka654f84e2013-04-19 11:55:01 +0200717 if (http_server != NULL) {
718 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: POP notifications for session");
719 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200720
Tomas Cejka654f84e2013-04-19 11:55:01 +0200721 while ((notif = (notification_t *) apr_array_pop(ls->notifications)) != NULL) {
722 char t[128];
Tomas Cejka73286932013-05-27 22:54:35 +0200723 memset(&t, 0, 128);
Tomas Cejka654f84e2013-04-19 11:55:01 +0200724 strftime(t, sizeof(t), "%c", localtime(&notif->eventtime));
725 n = 0;
Tomas Cejka73286932013-05-27 22:54:35 +0200726 json_object *notif_json = json_object_new_object();
727 json_object_object_add(notif_json, "eventtime", json_object_new_string(t));
728 json_object_object_add(notif_json, "content", json_object_new_string(notif->content));
729
Tomas Cejka00635972013-06-03 15:10:52 +0200730 const char *msgtext = json_object_to_json_string(notif_json);
Tomas Cejka15c56302013-05-30 01:11:30 +0200731
732 n = sprintf((char *)p, "%s", msgtext);
733 m = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
734
Tomas Cejka8a82dab2013-05-30 23:37:23 +0200735 json_object_put(notif_json);
Tomas Cejka654f84e2013-04-19 11:55:01 +0200736 }
737 if (http_server != NULL) {
738 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: POP notifications done");
739 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200740 }
741
742 if (pthread_mutex_unlock(&ls->lock) != 0) {
743 #ifndef TEST_NOTIFICATION_SERVER
744 if (http_server != NULL) {
745 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notification: cannot unlock session");
746 }
747 #endif
748 }
Tomas Cejka654f84e2013-04-19 11:55:01 +0200749 //if (http_server != NULL) {
750 // ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: unlocked session lock");
751 //}
Tomas Cejkaba21b382013-04-13 02:37:32 +0200752
753
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100754 if (m < n) {
Tomas Cejka15c56302013-05-30 01:11:30 +0200755 if (http_server != NULL) {
756 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "ERROR %d writing to di socket\n", n);
757 }
758
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100759 return -1;
760 }
761 if (close_testing && pss->number == 50) {
Tomas Cejka15c56302013-05-30 01:11:30 +0200762 if (http_server != NULL) {
763 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "close tesing limit, closing\n");
764 }
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100765 return -1;
766 }
767 break;
768
769 case LWS_CALLBACK_RECEIVE:
Tomas Cejkaba21b382013-04-13 02:37:32 +0200770 #ifndef TEST_NOTIFICATION_SERVER
771 if (http_server != NULL) {
772 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "received: (%s)", (char *)in);
773 }
774 #endif
775 if (pss->session_key == NULL) {
776 char session_key_buf[41];
777 int start = -1;
778 time_t stop = time(NULL) + 30;
779
780 strncpy((char *) session_key_buf, (const char *) in, 40);
781 session_key_buf[40] = '\0';
782 pss->session_key = strdup(session_key_buf);
783 sscanf(in+40, "%d %d", (int *) &start, (int *) &stop);
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200784 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "notification: get key (%s) from (%s) (%i,%i)", pss->session_key, (char *) in, (int) start, (int) stop);
Tomas Cejka15c56302013-05-30 01:11:30 +0200785
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200786 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "lock session lock");
787 if (pthread_rwlock_rdlock (&session_lock) != 0) {
788 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
789 return -1;
790 }
791 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "get session from key (%s)", pss->session_key);
Tomas Cejkaba21b382013-04-13 02:37:32 +0200792 struct session_with_mutex *ls = get_ncsession_from_key(pss->session_key);
793 if (ls == NULL) {
794 if (http_server != NULL) {
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200795 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notification: session_key not found (%s)", pss->session_key);
Tomas Cejkaba21b382013-04-13 02:37:32 +0200796 }
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200797 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "unlock session lock");
798 if (pthread_rwlock_unlock (&session_lock) != 0) {
799 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
800 return -1;
801 }
802 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Close notification client");
803 return -1;
804 }
805 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Found session to subscribe notif.");
806 if (ls->closed == 1) {
807 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "session already closed - handle no notification");
808 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "unlock session lock");
809 if (pthread_rwlock_unlock (&session_lock) != 0) {
810 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
811 return -1;
812 }
813 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Close notification client");
814 return -1;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200815 }
Tomas Cejka654f84e2013-04-19 11:55:01 +0200816 if (ls->ntfc_subscribed != 0) {
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200817 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: already subscribed");
818 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "unlock session lock");
819 if (pthread_rwlock_unlock (&session_lock) != 0) {
820 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
821 return -1;
822 }
823 /* do not close client, only do not subscribe again */
Tomas Cejka654f84e2013-04-19 11:55:01 +0200824 return 0;
825 }
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200826 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notification: prepare to subscribe stream");
827 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "unlock session lock");
828 if (pthread_rwlock_unlock (&session_lock) != 0) {
829 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
830 return -1;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200831 }
Tomas Cejka654f84e2013-04-19 11:55:01 +0200832 //if (pthread_rwlock_rdlock (&session_lock) != 0) {
833 // ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200834 // return -1;
Tomas Cejka654f84e2013-04-19 11:55:01 +0200835 //}
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200836 return notif_subscribe(ls, pss->session_key, (time_t) start, (time_t) stop);
Tomas Cejka654f84e2013-04-19 11:55:01 +0200837 //if (pthread_rwlock_unlock (&session_lock) != 0) {
838 // ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
Tomas Cejkabdedcd32013-06-09 11:54:53 +0200839 // return -1;
Tomas Cejka654f84e2013-04-19 11:55:01 +0200840 //}
Tomas Cejkaba21b382013-04-13 02:37:32 +0200841 }
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100842 if (len < 6)
843 break;
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100844 break;
845 /*
846 * this just demonstrates how to use the protocol filter. If you won't
847 * study and reject connections based on header content, you don't need
848 * to handle this callback
849 */
850
851 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
852 //dump_handshake_info(wsi);
853 /* you could return non-zero here and kill the connection */
854 break;
855
856 default:
857 break;
858 }
859
860 return 0;
861}
862
863/* list of supported protocols and callbacks */
864
865static struct libwebsocket_protocols protocols[] = {
866 /* first protocol must always be HTTP handler */
867
868 {
869 "http-only", /* name */
870 callback_http, /* callback */
871 sizeof (struct per_session_data__http), /* per_session_data_size */
872 0, /* max frame size / rx buffer */
873 },
874 {
875 "notification-protocol",
876 callback_notification,
Tomas Cejkaba21b382013-04-13 02:37:32 +0200877 sizeof(struct per_session_data__notif_client),
878 80,
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100879 },
880 { NULL, NULL, 0, 0 } /* terminator */
881};
882
883
Tomas Cejkaba21b382013-04-13 02:37:32 +0200884/**
885 * initialization of notification module
886 */
887int notification_init(apr_pool_t * pool, server_rec * server, apr_hash_t *conns)
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100888{
Tomas Cejkaba21b382013-04-13 02:37:32 +0200889 //char cert_path[1024];
890 //char key_path[1024];
891 //int use_ssl = 0;
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100892 struct lws_context_creation_info info;
893 int opts = 0;
Tomas Cejkaba21b382013-04-13 02:37:32 +0200894 //char interface_name[128] = "";
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100895 const char *iface = NULL;
896 int debug_level = 7;
897
Tomas Cejkaba21b382013-04-13 02:37:32 +0200898 netconf_locked_sessions = conns;
899
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100900 memset(&info, 0, sizeof info);
901 info.port = NOTIFICATION_SERVER_PORT;
902
903 /* tell the library what debug level to emit and to send it to syslog */
904 lws_set_log_level(debug_level, lwsl_emit_syslog);
905
906 #ifndef TEST_NOTIFICATION_SERVER
907 if (server != NULL) {
908 http_server = server;
909 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "Initialization of libwebsocket");
910 }
911 #endif
Tomas Cejka15c56302013-05-30 01:11:30 +0200912 //lwsl_notice("libwebsockets test server - "
913 // "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
914 // "licensed under LGPL2.1\n");
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100915 max_poll_elements = getdtablesize();
916 pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
917 fd_lookup = malloc(max_poll_elements * sizeof (int));
918 if (pollfds == NULL || fd_lookup == NULL) {
Tomas Cejka15c56302013-05-30 01:11:30 +0200919 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "Out of memory pollfds=%d\n", max_poll_elements);
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100920 return -1;
921 }
922
923 info.iface = iface;
924 info.protocols = protocols;
925
926 //snprintf(cert_path, sizeof(cert_path), "%s/libwebsockets-test-server.pem", resource_path);
927 //snprintf(key_path, sizeof(cert_path), "%s/libwebsockets-test-server.key.pem", resource_path);
928
929 //info.ssl_cert_filepath = cert_path;
930 //info.ssl_private_key_filepath = key_path;
931
932 info.gid = -1;
933 info.uid = -1;
934 info.options = opts;
935
936 /* create server */
937 context = libwebsocket_create_context(&info);
938 if (context == NULL) {
Tomas Cejka15c56302013-05-30 01:11:30 +0200939 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "libwebsocket init failed.");
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100940 return -1;
941 }
Tomas Cejka15c56302013-05-30 01:11:30 +0200942
943 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, http_server, "notifications: init of pthread_key_create.");
944 if (pthread_key_create(&thread_key, NULL) != 0) {
945 #ifndef TEST_NOTIFICATION_SERVER
946 if (http_server != NULL) {
947 ap_log_error(APLOG_MARK, APLOG_ERR, 0, http_server, "notifications: pthread_key_create failed");
948 }
949 #endif
950 }
Tomas Cejkaba21b382013-04-13 02:37:32 +0200951 return 0;
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100952}
953
954void notification_close()
955{
956 libwebsocket_context_destroy(context);
957
Tomas Cejka15c56302013-05-30 01:11:30 +0200958 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, http_server, "libwebsockets-test-server exited cleanly\n");
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100959}
960
Tomas Cejkaba21b382013-04-13 02:37:32 +0200961
Tomas Cejkad340dbf2013-03-24 20:36:57 +0100962/**
963 * \brief send notification if any
964 * \return < 0 on error
965 */
966int notification_handle()
967{
968 static struct timeval tv;
969 static unsigned int olds = 0;
970 int n = 0;
971
972 gettimeofday(&tv, NULL);
973
974 /*
975 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
976 * live websocket connection using the DUMB_INCREMENT protocol,
977 * as soon as it can take more packets (usually immediately)
978 */
979
980 if (((unsigned int)tv.tv_sec - olds) > 0) {
981 libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_NOTIFICATION]);
982 olds = tv.tv_sec;
983 }
984
985
986 /*
987 * this represents an existing server's single poll action
988 * which also includes libwebsocket sockets
989 */
990
991 n = poll(pollfds, count_pollfds, 50);
992 if (n < 0)
993 return n;
994
995
996 if (n) {
997 for (n = 0; n < count_pollfds; n++) {
998 if (pollfds[n].revents) {
999 /*
1000 * returns immediately if the fd does not
1001 * match anything under libwebsockets
1002 * control
1003 */
1004 if (libwebsocket_service_fd(context, &pollfds[n]) < 0) {
1005 return 1;
1006 }
1007 }
1008 }
1009 }
1010 return 0;
1011}
1012
1013#endif
1014
1015
1016#ifndef WITH_NOTIFICATIONS
1017#ifdef TEST_NOTIFICATION_SERVER
1018int main(int argc, char **argv)
1019{
Tomas Cejkad340dbf2013-03-24 20:36:57 +01001020 if (notification_init(NULL, NULL) == -1) {
1021 fprintf(stderr, "Error during initialization\n");
1022 return 1;
1023 }
1024 while (!force_exit) {
1025 notification_handle();
1026 }
1027 notification_close();
1028}
1029#endif
1030#endif