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