1 /*
2  *  tvheadend, COMET
3  *  Copyright (C) 2008 Andreas Öman
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <pthread.h>
20 #include <assert.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <arpa/inet.h>
26 #include <sys/socket.h>
27 #include <openssl/sha.h>
28 
29 #include "htsmsg.h"
30 #include "htsmsg_json.h"
31 
32 #include "tvheadend.h"
33 #include "config.h"
34 #include "http.h"
35 #include "dvr/dvr.h"
36 #include "webui/webui.h"
37 #include "access.h"
38 #include "notify.h"
39 #include "tcp.h"
40 
41 static pthread_mutex_t comet_mutex = PTHREAD_MUTEX_INITIALIZER;
42 static tvh_cond_t comet_cond;
43 static int comet_waiting;
44 
45 #define MAILBOX_UNUSED_TIMEOUT      20
46 #define MAILBOX_EMPTY_REPLY_TIMEOUT 10
47 
48 //#define mbdebug(fmt...) printf(fmt);
49 #define mbdebug(fmt...)
50 
51 
52 static LIST_HEAD(, comet_mailbox) mailboxes;
53 
54 int mailbox_tally;
55 int comet_running;
56 
57 typedef struct comet_mailbox {
58   char *cmb_boxid; /* SHA-1 hash */
59   char *cmb_lang;  /* UI language */
60   int cmb_restricted; /* !admin */
61   htsmsg_t *cmb_messages; /* A vector */
62   int64_t cmb_last_used;
63   LIST_ENTRY(comet_mailbox) cmb_link;
64   int cmb_debug;
65 } comet_mailbox_t;
66 
67 
68 /**
69  *
70  */
71 static void
cmb_destroy(comet_mailbox_t * cmb)72 cmb_destroy(comet_mailbox_t *cmb)
73 {
74   mbdebug("mailbox[%s]: destroyed\n", cmb->cmb_boxid);
75 
76   if(cmb->cmb_messages != NULL)
77     htsmsg_destroy(cmb->cmb_messages);
78 
79   LIST_REMOVE(cmb, cmb_link);
80 
81   free(cmb->cmb_lang);
82   free(cmb->cmb_boxid);
83   free(cmb);
84 }
85 
86 
87 /**
88  *
89  */
90 void
comet_flush(void)91 comet_flush(void)
92 {
93   comet_mailbox_t *cmb, *next;
94 
95   pthread_mutex_lock(&comet_mutex);
96 
97   for(cmb = LIST_FIRST(&mailboxes); cmb != NULL; cmb = next) {
98     next = LIST_NEXT(cmb, cmb_link);
99 
100     if(cmb->cmb_last_used && cmb->cmb_last_used + sec2mono(60) < mclk())
101       cmb_destroy(cmb);
102   }
103   pthread_mutex_unlock(&comet_mutex);
104 }
105 
106 
107 /**
108  *
109  */
110 static comet_mailbox_t *
comet_mailbox_create(const char * lang)111 comet_mailbox_create(const char *lang)
112 {
113   comet_mailbox_t *cmb = calloc(1, sizeof(comet_mailbox_t));
114 
115   struct timeval tv;
116   uint8_t sum[20];
117   char id[20 * 2 + 1];
118   int i;
119   SHA_CTX sha1;
120 
121   gettimeofday(&tv, NULL);
122 
123   SHA1_Init(&sha1);
124   SHA1_Update(&sha1, (void *)&tv, sizeof(tv));
125   SHA1_Update(&sha1, (void *)&mailbox_tally, sizeof(uint32_t));
126   SHA1_Final(sum, &sha1);
127 
128   for(i = 0; i < sizeof(sum); i++) {
129     id[i * 2 + 0] = "0123456789abcdef"[sum[i] >> 4];
130     id[i * 2 + 1] = "0123456789abcdef"[sum[i] & 15];
131   }
132   id[40] = 0;
133 
134   cmb->cmb_boxid = strdup(id);
135   cmb->cmb_lang = lang ? strdup(lang) : NULL;
136   cmb->cmb_last_used = mclk();
137   mailbox_tally++;
138 
139   LIST_INSERT_HEAD(&mailboxes, cmb, cmb_link);
140   return cmb;
141 }
142 
143 /**
144  *
145  */
146 static void
comet_access_update(http_connection_t * hc,comet_mailbox_t * cmb)147 comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb)
148 {
149   extern int access_noacl;
150 
151   htsmsg_t *m = htsmsg_create_map();
152   const char *username = "";
153   int64_t bfree, bused, btotal;
154   int dvr = !http_access_verify(hc, ACCESS_RECORDER);
155   int admin = !http_access_verify(hc, ACCESS_ADMIN);
156   const char *s;
157 
158   htsmsg_add_str(m, "notificationClass", "accessUpdate");
159 
160   if (hc->hc_access) {
161     username = hc->hc_access->aa_username ?: "";
162 
163     switch (hc->hc_access->aa_uilevel) {
164     case UILEVEL_BASIC:    s = "basic";    break;
165     case UILEVEL_ADVANCED: s = "advanced"; break;
166     case UILEVEL_EXPERT:   s = "expert";   break;
167     default:               s = NULL;       break;
168     }
169     if (s) {
170       htsmsg_add_str(m, "uilevel", s);
171       if (config.uilevel_nochange)
172         htsmsg_add_u32(m, "uilevel_nochange", config.uilevel_nochange);
173     }
174   }
175   htsmsg_add_str(m, "theme", access_get_theme(hc->hc_access));
176   htsmsg_add_u32(m, "quicktips", config.ui_quicktips);
177   if (!access_noacl)
178     htsmsg_add_str(m, "username", username);
179   if (hc->hc_peer_ipstr)
180     htsmsg_add_str(m, "address", hc->hc_peer_ipstr);
181   htsmsg_add_u32(m, "dvr",      dvr);
182   htsmsg_add_u32(m, "admin",    admin);
183 
184   htsmsg_add_s64(m, "time",     time(NULL));
185 
186   if (config.cookie_expires)
187     htsmsg_add_u32(m, "cookie_expires", config.cookie_expires);
188 
189   if (config.info_area && config.info_area[0])
190     htsmsg_add_str(m, "info_area", config.info_area);
191 
192   if (dvr && !dvr_get_disk_space(&bfree, &bused, &btotal)) {
193     htsmsg_add_s64(m, "freediskspace", bfree);
194     htsmsg_add_s64(m, "useddiskspace", bused);
195     htsmsg_add_s64(m, "totaldiskspace", btotal);
196   }
197 
198   if (admin && config.wizard)
199     htsmsg_add_str(m, "wizard", config.wizard);
200 
201   if(cmb->cmb_messages == NULL)
202     cmb->cmb_messages = htsmsg_create_list();
203   htsmsg_add_msg(cmb->cmb_messages, NULL, m);
204 }
205 
206 /**
207  *
208  */
209 static void
comet_serverIpPort(http_connection_t * hc,comet_mailbox_t * cmb)210 comet_serverIpPort(http_connection_t *hc, comet_mailbox_t *cmb)
211 {
212   char buf[50];
213   uint32_t port;
214 
215   tcp_get_str_from_ip(hc->hc_self, buf, 50);
216 
217   if(hc->hc_self->ss_family == AF_INET)
218     port = ((struct sockaddr_in*)hc->hc_self)->sin_port;
219   else if(hc->hc_self->ss_family == AF_INET6)
220     port = ((struct sockaddr_in6*)hc->hc_self)->sin6_port;
221   else
222     port = 0;
223 
224   htsmsg_t *m = htsmsg_create_map();
225 
226   htsmsg_add_str(m, "notificationClass", "setServerIpPort");
227 
228   htsmsg_add_str(m, "ip", buf);
229   htsmsg_add_u32(m, "port", ntohs(port));
230 
231   if(cmb->cmb_messages == NULL)
232     cmb->cmb_messages = htsmsg_create_list();
233   htsmsg_add_msg(cmb->cmb_messages, NULL, m);
234 }
235 
236 
237 /**
238  * Poll callback
239  */
240 static int
comet_mailbox_poll(http_connection_t * hc,const char * remain,void * opaque)241 comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque)
242 {
243   comet_mailbox_t *cmb = NULL;
244   const char *cometid = http_arg_get(&hc->hc_req_args, "boxid");
245   const char *immediate = http_arg_get(&hc->hc_req_args, "immediate");
246   const char *lang = hc->hc_access->aa_lang_ui;
247   int im = immediate ? atoi(immediate) : 0, e;
248   int64_t mono;
249   htsmsg_t *m;
250 
251   if(!im)
252     tvh_safe_usleep(100000); /* Always sleep 0.1 sec to avoid comet storms */
253 
254   pthread_mutex_lock(&comet_mutex);
255   if (!atomic_get(&comet_running)) {
256     pthread_mutex_unlock(&comet_mutex);
257     return HTTP_STATUS_BAD_REQUEST;
258   }
259 
260   if(cometid != NULL)
261     LIST_FOREACH(cmb, &mailboxes, cmb_link)
262       if(!strcmp(cmb->cmb_boxid, cometid))
263 	break;
264 
265   if(cmb == NULL) {
266     cmb = comet_mailbox_create(lang);
267     comet_access_update(hc, cmb);
268     comet_serverIpPort(hc, cmb);
269   }
270   cmb->cmb_last_used = 0; /* Make sure we're not flushed out */
271   if (http_access_verify(hc, ACCESS_ADMIN)) {
272     if (!cmb->cmb_restricted) {
273       cmb->cmb_restricted = 1;
274       pthread_mutex_unlock(&comet_mutex);
275       comet_mailbox_add_logmsg(tvh_gettext_lang(lang, N_("Restricted log mode (no administrator)")), 0, 0);
276       pthread_mutex_lock(&comet_mutex);
277     }
278   }
279 
280   if(!im && cmb->cmb_messages == NULL) {
281     mono = mclk() + sec2mono(10);
282     comet_waiting++;
283     do {
284       e = tvh_cond_timedwait(&comet_cond, &comet_mutex, mono);
285       if (e == ETIMEDOUT)
286         break;
287     } while (ERRNO_AGAIN(e));
288     comet_waiting--;
289     if (!atomic_get(&comet_running)) {
290       pthread_mutex_unlock(&comet_mutex);
291       return 400;
292     }
293   }
294 
295   m = htsmsg_create_map();
296   htsmsg_add_str(m, "boxid", cmb->cmb_boxid);
297   htsmsg_add_msg(m, "messages", cmb->cmb_messages ?: htsmsg_create_list());
298   cmb->cmb_messages = NULL;
299 
300   cmb->cmb_last_used = mclk();
301 
302   pthread_mutex_unlock(&comet_mutex);
303 
304   htsmsg_json_serialize(m, &hc->hc_reply, 0);
305   htsmsg_destroy(m);
306   http_output_content(hc, "text/x-json; charset=UTF-8");
307   return 0;
308 }
309 
310 
311 /**
312  * Poll callback
313  */
314 static int
comet_mailbox_dbg(http_connection_t * hc,const char * remain,void * opaque)315 comet_mailbox_dbg(http_connection_t *hc, const char *remain, void *opaque)
316 {
317   comet_mailbox_t *cmb = NULL;
318   const char *cometid = http_arg_get(&hc->hc_req_args, "boxid");
319   const char *lang = hc->hc_access->aa_lang_ui;
320   const char *s;
321 
322   if(cometid == NULL)
323     return 400;
324 
325   pthread_mutex_lock(&comet_mutex);
326 
327   LIST_FOREACH(cmb, &mailboxes, cmb_link) {
328     if(!strcmp(cmb->cmb_boxid, cometid)) {
329       char buf[64];
330       cmb->cmb_debug = !cmb->cmb_debug;
331 
332       if(cmb->cmb_messages == NULL)
333 	cmb->cmb_messages = htsmsg_create_list();
334 
335       if(cmb->cmb_restricted || http_access_verify(hc, ACCESS_ADMIN))
336         s = N_("Only admin can watch the realtime log.");
337       else if(cmb->cmb_debug)
338         s = N_("Loglevel debug: enabled");
339       else
340         s = N_("Loglevel debug: disabled");
341       snprintf(buf, sizeof(buf), "%s", tvh_gettext_lang(lang, s));
342 
343       htsmsg_t *m = htsmsg_create_map();
344       htsmsg_add_str(m, "notificationClass", "logmessage");
345       htsmsg_add_str(m, "logtxt", buf);
346       htsmsg_add_msg(cmb->cmb_messages, NULL, m);
347 
348       tvh_cond_signal(&comet_cond, 1);
349       break;
350     }
351   }
352   pthread_mutex_unlock(&comet_mutex);
353 
354   http_output_content(hc, "text/plain; charset=UTF-8");
355   return 0;
356 }
357 
358 /**
359  *
360  */
361 void
comet_init(void)362 comet_init(void)
363 {
364   pthread_mutex_lock(&comet_mutex);
365   tvh_cond_init(&comet_cond);
366   atomic_set(&comet_running, 1);
367   comet_waiting = 0;
368   pthread_mutex_unlock(&comet_mutex);
369   http_path_add("/comet/poll",  NULL, comet_mailbox_poll, ACCESS_WEB_INTERFACE);
370   http_path_add("/comet/debug", NULL, comet_mailbox_dbg,  ACCESS_WEB_INTERFACE);
371 }
372 
373 void
comet_done(void)374 comet_done(void)
375 {
376   comet_mailbox_t *cmb;
377   int waiting;
378 
379   pthread_mutex_lock(&comet_mutex);
380   atomic_set(&comet_running, 0);
381   while ((cmb = LIST_FIRST(&mailboxes)) != NULL)
382     cmb_destroy(cmb);
383   tvh_cond_signal(&comet_cond, 1);
384   pthread_mutex_unlock(&comet_mutex);
385 
386   waiting = 1;
387   while (waiting) {
388     pthread_mutex_lock(&comet_mutex);
389     waiting = comet_waiting;
390     pthread_mutex_unlock(&comet_mutex);
391   }
392 
393   tvh_cond_destroy(&comet_cond);
394 
395 }
396 
397 /**
398  *
399  */
400 static void
comet_mailbox_rewrite_str(htsmsg_t * m,const char * key,const char * lang)401 comet_mailbox_rewrite_str(htsmsg_t *m, const char *key, const char *lang)
402 {
403   const char *s = htsmsg_get_str(m, key), *p;
404   if (s) {
405     p = tvh_gettext_lang(lang, s);
406     if (p != s)
407       htsmsg_set_str(m, key, p);
408   }
409 }
410 
411 static void
comet_mailbox_rewrite_msg(int rewrite,htsmsg_t * m,const char * lang)412 comet_mailbox_rewrite_msg(int rewrite, htsmsg_t *m, const char *lang)
413 {
414   switch (rewrite) {
415   case NOTIFY_REWRITE_SUBSCRIPTIONS:
416     comet_mailbox_rewrite_str(m, "state", lang);
417     break;
418   }
419 }
420 
421 /**
422  *
423  */
424 void
comet_mailbox_add_message(htsmsg_t * m,int isdebug,int rewrite)425 comet_mailbox_add_message(htsmsg_t *m, int isdebug, int rewrite)
426 {
427   comet_mailbox_t *cmb;
428   htsmsg_t *e;
429 
430   if (!atomic_get(&comet_running))
431     return;
432 
433   pthread_mutex_lock(&comet_mutex);
434 
435   if (atomic_get(&comet_running)) {
436     LIST_FOREACH(cmb, &mailboxes, cmb_link) {
437 
438       if(cmb->cmb_restricted)
439         continue;
440 
441       if(isdebug && !cmb->cmb_debug)
442         continue;
443 
444       if(cmb->cmb_messages == NULL)
445         cmb->cmb_messages = htsmsg_create_list();
446       e = htsmsg_copy(m);
447       if (cmb->cmb_lang && rewrite)
448         comet_mailbox_rewrite_msg(rewrite, e, cmb->cmb_lang);
449       htsmsg_add_msg(cmb->cmb_messages, NULL, e);
450     }
451     tvh_cond_signal(&comet_cond, 1);
452   }
453 
454   pthread_mutex_unlock(&comet_mutex);
455 }
456 
457 /**
458  *
459  */
460 void
comet_mailbox_add_logmsg(const char * txt,int isdebug,int rewrite)461 comet_mailbox_add_logmsg(const char *txt, int isdebug, int rewrite)
462 {
463   htsmsg_t *m = htsmsg_create_map();
464   htsmsg_add_str(m, "notificationClass", "logmessage");
465   htsmsg_add_str(m, "logtxt", txt);
466   comet_mailbox_add_message(m, isdebug, 0);
467   htsmsg_destroy(m);
468 }
469