Statistics
| Revision:

root / trunk / plugins / jsonrpc / plugin_webserver.c @ 1834

History | View | Annotate | Download (15 KB)

1
/* 
2
    Copyright (C) 2005-2010  Erik van Pienbroek
3

                
4
    This program is free software; you can redistribute it and/or modify
5
    it under the terms of the GNU General Public License as published by
6
    the Free Software Foundation; either version 2 of the License, or
7
    (at your option) any later version.
8

                
9
    This program is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
    GNU General Public License for more details.
13

                
14
    You should have received a copy of the GNU General Public License
15
    along with this program; if not, write to the Free Software
16
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
*/
18

                
19
#ifdef HAVE_CONFIG_H
20
#include "config.h"
21
#endif
22

                
23
#include 
24
#include 
25
#include 
26
#include 
27
#include "nntpgrab_plugin.h"
28
#include "nntpgrab_utils.h"
29
#include "mongoose.h"
30
#include "json.h"
31
#include "jsonrpc.h"
32
#include "marshalers.h"
33

                
34
static struct mg_context *ctx = NULL;
35
static NGPlugin *plugin_data_global = NULL;
36
static int current_listen_port = 0;
37
static ngboolean ignore_config_changes = FALSE;
38

                
39
/* Webserver methods */
40
static void set_ignore_config_changes(NGPlugin *plugin_data);
41
static ngboolean start_webserver(NGPlugin *plugin_data, int port, char **errmsg);
42
static void stop_webserver(NGPlugin *plugin_data);
43
static void on_config_changed(NGPlugin *plugin_data, gpointer data);
44
static void on_log_message_received(struct mg_connection *conn, const struct mg_request_info *info, void *message);
45
static void process_favicon_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data);
46
static void process_jsonrpc_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data);
47
static void process_upload_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data);
48
void mongoose_hacks_set_plugin_data(NGPlugin *plugin_data);
49
ngboolean listen_on_port(struct mg_context *ctx, char *port);
50
void jsonrpc_tcp_force_disconnect(void);
51

                
52
/* JSON-RPC methods */
53
void jsonrpc_methods_register_methods(NGPlugin *plugin_data);
54
void jsonrpc_methods_unregister_methods(void);
55

                
56
/* JSON-RPC events */
57
void jsonrpc_connect_signal_handlers(NGPlugin *plugin_data);
58
void jsonrpc_disconnect_signal_handlers(NGPlugin *plugin_data);
59

                
60
void
61
nntpgrab_plugin_initialize(NGPlugin *plugin_data)
62
{
63
    ng_plugin_set_name(plugin_data, "JSON-RPC");
64
    ng_plugin_set_version(plugin_data, PACKAGE_VERSION);
65
    ng_plugin_set_author(plugin_data, "Erik van Pienbroek");
66
    ng_plugin_set_url(plugin_data, "https://www.nntpgrab.nl");
67
    ng_plugin_set_description(plugin_data, "Allow NNTPGrab to be remotely controlled using JSON-RPC");
68

                
69
    ng_plugin_register_function(plugin_data,
70
                                "webserver_start",
71
                                NG_PLUGIN_FUNCTION(start_webserver),
72
                                ng_plugin_marshal_BOOLEAN__INT_POINTER,
73
                                G_TYPE_BOOLEAN,
74
                                2, G_TYPE_INT, G_TYPE_POINTER);
75

                
76
    ng_plugin_register_function(plugin_data,
77
                                "webserver_ignore_config_changes",
78
                                NG_PLUGIN_FUNCTION(set_ignore_config_changes),
79
                                ng_plugin_marshal_VOID__VOID,
80
                                G_TYPE_NONE,
81
                                0);
82

                
83
    ng_plugin_set_required_event(plugin_data, "config_changed");
84
    ng_plugin_set_required_event(plugin_data, "part_download_start");
85
    ng_plugin_set_required_event(plugin_data, "part_done");
86
    ng_plugin_set_required_event(plugin_data, "part_failed");
87
    ng_plugin_set_required_event(plugin_data, "traffic_monitor_update");
88
    ng_plugin_set_required_event(plugin_data, "part_progress_update");
89
    ng_plugin_set_required_event(plugin_data, "collection_added");
90
    ng_plugin_set_required_event(plugin_data, "collection_removed");
91
    ng_plugin_set_required_event(plugin_data, "collection_modified");
92
    ng_plugin_set_required_event(plugin_data, "file_added");
93
    ng_plugin_set_required_event(plugin_data, "file_removed");
94
    ng_plugin_set_required_event(plugin_data, "file_download_state_update");
95
    ng_plugin_set_required_event(plugin_data, "file_state_changed");
96
    ng_plugin_set_required_event(plugin_data, "connection_connecting");
97
    ng_plugin_set_required_event(plugin_data, "connection_connected");
98
    ng_plugin_set_required_event(plugin_data, "connection_disconnect");
99
    ng_plugin_set_required_event(plugin_data, "schedular_state_changed");
100
    ng_plugin_set_required_event(plugin_data, "log_message");
101
    ng_plugin_set_required_event(plugin_data, "task_moved");
102
    ng_plugin_set_required_event(plugin_data, "collection_moved");
103
    ng_plugin_set_required_event(plugin_data, "all_downloads_completed");
104
    ng_plugin_set_required_event(plugin_data, "plugin_loaded");
105
    ng_plugin_set_required_event(plugin_data, "plugin_unloaded");
106
    ng_plugin_set_required_event(plugin_data, "plugin_event");
107

                
108
    ng_plugin_create_event(plugin_data, "num_active_connections_changed", 1);
109
}
110

                
111
ngboolean
112
nntpgrab_plugin_load(NGPlugin *plugin_data, char **errmsg)
113
{
114
    NGConfigOpts opts;
115

                
116
    g_return_val_if_fail(plugin_data != NULL, FALSE);
117
    g_return_val_if_fail(errmsg != NULL, FALSE);
118
    g_return_val_if_fail(ctx == NULL, FALSE);
119

                
120
    plugin_data_global = plugin_data;
121

                
122
    jsonrpc_methods_register_methods(plugin_data);
123

                
124
    ng_plugin_connect_event(plugin_data, "config_changed", NG_PLUGIN_FUNCTION(on_config_changed), NULL);
125

                
126
    opts = plugin_data->core_funcs.config_get_opts();
127
    if (!opts.enable_webserver) {
128
        return TRUE;
129
    }
130

                
131
    return start_webserver(plugin_data, opts.webserver_port, errmsg);
132
}
133

                
134
static void
135
set_ignore_config_changes(NGPlugin *plugin_data)
136
{
137
    ignore_config_changes = TRUE;
138
}
139

                
140
static ngboolean
141
start_webserver(NGPlugin *plugin_data, int port, char **errmsg)
142
{
143
    char *path;
144
    char port_str[16];
145
    char opt[128];
146

                
147
    g_return_val_if_fail(plugin_data != NULL, FALSE);
148
    g_return_val_if_fail(errmsg != NULL, FALSE);
149

                
150
    if (ctx) {
151
        if (current_listen_port == port) {
152
            return TRUE;
153
        }
154

                
155
        stop_webserver(plugin_data);
156
    }
157

                
158
    memset(&port_str, 0, sizeof(port_str));
159
    snprintf(port_str, sizeof(port_str) - 1, "%i", port);
160

                
161
    if ((ctx = mg_start()) == NULL) {
162
        *errmsg = g_strdup(_("Unable to start embedded webserver"));
163
        return FALSE;
164
    }
165

                
166
    mg_set_log_callback(ctx, on_log_message_received);
167

                
168
    listen_on_port(ctx, port_str);
169

                
170
    mg_set_option(ctx, "idle_time", "3");
171
    mg_set_option(ctx, "dir_list", "0");
172
    if (g_getenv("NNTPGRAB_WWWDIR")) {
173
        path = (char*) g_getenv("NNTPGRAB_WWWDIR");
174
    } else {
175
        path = WWW_DIR;
176
    }
177
    mg_set_option(ctx, "root", path);
178
    mg_set_option(ctx, "auth_realm", "NNTPGrab embedded webserver");
179

                
180
    if (g_getenv("NNTPGRAB_CONFIG_DIR")) {
181
        path = g_build_filename(g_getenv("NNTPGRAB_CONFIG_DIR"), "NNTPGrab", "htpasswd", NULL);
182
    } else {
183
        path = g_build_filename(g_get_user_config_dir(), "NNTPGrab", "htpasswd", NULL);
184
    }
185
    if (!g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
186
        /* Password file doesn't exist yet, generate a default one */
187
        mg_modify_passwords_file(ctx, path, "admin", "admin");
188
    }
189
    memset(&opt, 0, sizeof(opt));
190
    snprintf(opt, sizeof(opt) - 1, "/jsonrpc=%s", path);
191
    mg_set_option(ctx, "protect", opt);
192
    g_free(path);
193

                
194
    //mg_set_uri_callback(ctx, "/", &process_index_request, NULL);
195
    mg_set_uri_callback(ctx, "/favicon.ico", &process_favicon_request, NULL);
196
    mg_set_uri_callback(ctx, "/jsonrpc", &process_jsonrpc_request, NULL);
197
    mg_set_uri_callback(ctx, "/upload", &process_upload_request, plugin_data);
198

                
199
    mongoose_hacks_set_plugin_data(plugin_data);
200
    jsonrpc_connect_signal_handlers(plugin_data);
201

                
202
    current_listen_port = port;
203

                
204
    return TRUE;
205
}
206

                
207
ngboolean
208
nntpgrab_plugin_can_unload(NGPlugin *plugin_data, char **reason)
209
{
210
    return TRUE;
211
}
212

                
213
void
214
nntpgrab_plugin_unload(NGPlugin *plugin_data)
215
{
216
    NGConfigOpts opts = plugin_data->core_funcs.config_get_opts();
217

                
218
    g_return_if_fail(plugin_data != NULL);
219

                
220
    if (opts.enable_webserver) {
221
        g_return_if_fail(ctx != NULL);
222

                
223
        stop_webserver(plugin_data);
224
    }
225

                
226
    mongoose_hacks_set_plugin_data(NULL);
227
    plugin_data_global = NULL;
228

                
229
    /* Free the buffer containing the most recent JSON-RPC events */
230
    jsonrpc_set_event_list_size(0);
231
}
232

                
233
static void
234
stop_webserver(NGPlugin *plugin_data)
235
{
236
    g_return_if_fail(plugin_data != NULL);
237
    g_return_if_fail(ctx != NULL);
238

                
239
    jsonrpc_tcp_force_disconnect();
240

                
241
    mg_stop(ctx);
242
    ctx = NULL;
243
    current_listen_port = 0;
244

                
245
    jsonrpc_disconnect_signal_handlers(plugin_data);
246
}
247

                
248
void
249
nntpgrab_plugin_destroy(NGPlugin *plugin_data)
250
{
251
    jsonrpc_methods_unregister_methods();
252
}
253

                
254
int
255
nntpgrab_plugin_get_version(void)
256
{
257
    return NNTPGRAB_PLUGIN_API_VERSION;
258
}
259

                
260
static void
261
on_config_changed(NGPlugin *plugin_data, gpointer data)
262
{
263
    NGConfigOpts opts = plugin_data->core_funcs.config_get_opts();
264
    char *errmsg = NULL;
265

                
266
    if (ignore_config_changes) {
267
        return;
268
    }
269

                
270
    /* Do we need to stop the embedded webserver? */
271
    if (!opts.enable_webserver && ctx != NULL) {
272
        stop_webserver(plugin_data);
273
        return;
274
    }
275

                
276
    /* Do we need to start the embedded webserver? */
277
    if (opts.enable_webserver && ctx == NULL) {
278
        if (!start_webserver(plugin_data, opts.webserver_port, &errmsg)) {
279
            ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_WARNING, _("Unable to start the embedded webserver: %s"), errmsg);
280
            g_free(errmsg);
281
        }
282
    }
283

                
284
    /* Did the webserver port change? */
285
    if (opts.enable_webserver && opts.webserver_port != current_listen_port) {
286
        stop_webserver(plugin_data);
287
        if (!start_webserver(plugin_data, opts.webserver_port, &errmsg)) {
288
            ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_WARNING, _("Unable to re-start the embedded webserver: %s"), errmsg);
289
            g_free(errmsg);
290
        }
291
    }
292
}
293

                
294
static void
295
process_favicon_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data)
296
{
297
    mg_printf(conn, "%s", "HTTP/1.1 404 File not found\r\n\r\n");
298
}
299

                
300
static void
301
process_jsonrpc_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data)
302
{
303
    char *response;
304
    char post_data[1024];
305
    int response_length = 0;
306

                
307
    mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
308
    mg_printf(conn, "%s", "Content-Type: text/plain\r\n");
309

                
310
    if (strcmp(request_info->request_method, "POST") ||
311
        request_info->post_data_len == 0) {
312

                
313
        mg_printf(conn, "\r\n%s", "This URI should only be used for JSON-RPC requests\r\n");
314
        return;
315
    }
316

                
317
    /* We need to duplicate the post_data here as it isn't NULL-terminated.. */
318
    if (request_info->post_data_len > sizeof(post_data) - 1) {
319
        /* Refuse to handle such a large request */
320
        mg_printf(conn, "\r\n%s", "Request is too large to process\r\n");
321
        return;
322
    }
323
    memset(&post_data, 0, sizeof(post_data));
324
    strncpy(post_data, request_info->post_data, request_info->post_data_len);
325
    response = jsonrpc_process(post_data, conn);
326

                
327
    g_print("post_data = %s\n", post_data);
328
    g_print("post_data_len = %i\n", request_info->post_data_len);
329
    g_print("response = %s\n", response);
330

                
331
    g_return_if_fail(response != NULL);
332

                
333
    response_length = strlen(response);
334

                
335
    mg_printf(conn, "Content-Length: %d\r\n\r\n", response_length);
336
    mg_write(conn, response, response_length);
337

                
338
    free(response);
339
}
340

                
341
static void
342
process_upload_request(struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data)
343
{
344
    NGPlugin *plugin_data = (NGPlugin*) user_data;
345
    NNTPGrabNZB *nzb;
346
    NGList *list;
347
    char *errmsg = NULL;
348
    char *warnings = NULL;
349
    char *nzb_data;
350
    char *collection_name;
351

                
352
    g_return_if_fail(plugin_data != NULL);
353

                
354
    if (strcmp(request_info->request_method, "POST") ||
355
        request_info->post_data_len == 0) {
356

                
357
        mg_printf(conn, "%s", "HTTP/1.1 400 Bad request\r\n");
358
        mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
359

                
360
        mg_printf(conn, "%s", "This URI should only be used for NZB upload requests\r\n");
361
        return;
362
    }
363

                
364
    collection_name = mg_get_var(conn, "collection_name");
365
    if (!collection_name) {
366
        mg_printf(conn, "%s", "HTTP/1.1 400 Bad request\r\n");
367
        mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
368

                
369
        mg_printf(conn, "%s", "The argument collection_name is missing\r\n");
370
        return;
371
    }
372

                
373
    nzb_data = mg_get_var(conn, "nzb_data");
374
    if (!nzb_data) {
375
        mg_printf(conn, "%s", "HTTP/1.1 400 Bad request\r\n");
376
        mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
377

                
378
        mg_printf(conn, "%s", "The argument nzb_data is missing\r\n");
379
        return;
380
    }
381

                
382
    g_print("collection_name = %s\n", collection_name);
383
    g_print("nzb_data = %s\n", nzb_data);
384
    g_print("post_data_len = %i\n", request_info->post_data_len);
385

                
386
    mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n");
387
    mg_printf(conn, "%s", "Content-Type: text/plain\r\n\r\n");
388

                
389
    /* Try to parse the NZB file */
390
    if (!(nzb = nntpgrab_utils_parse_nzb_file(nzb_data, &errmsg))) {
391
        mg_printf(conn, "ERROR: Unable to parse NZB file: %s\r\n", errmsg);
392
        ng_free(errmsg);
393
        goto out;
394
    }
395

                
396
    /* Add all the files to the download queue */
397
    list = nzb->files;
398
    while (list) {
399
        NNTPGrabNZBFile *file = list->data;
400

                
401
        if (!plugin_data->core_funcs.schedular_add_file_to_queue(collection_name, file->subject, file->poster, file->stamp, file->filesize, file->groups, file->segments, &errmsg)) {
402
            if (warnings) {
403
                char *tmp = g_strdup_printf("%s\n%s", warnings, errmsg);
404
                g_free(warnings);
405
                warnings = tmp;
406
            } else {
407
                warnings = g_strdup_printf(_("File could not be added to the download queue:\r\n%s"), errmsg);
408
            }
409
            g_free(errmsg);
410
            errmsg = NULL;
411
        }
412

                
413
        list = ng_list_next(list);
414
    }
415

                
416
    /* Save the download queue */
417
    if (!plugin_data->core_funcs.schedular_save_queue(&errmsg)) {
418
        char *msg;
419
        if (errmsg) {
420
            msg = g_strdup_printf(_("Download queue could not be saved:\r\n%s"), errmsg);
421
            g_free(errmsg);
422
        } else {
423
            msg = g_strdup_printf(_("Download queue could not be saved"));
424
        }
425

                
426
        mg_printf(conn, "ERROR: %s\r\n", msg);
427
        g_free(msg);
428
        goto out;
429
    }
430

                
431
    g_print("warnings = %s\n", warnings);
432

                
433
    if (!warnings) {
434
        mg_printf(conn, "OK: NZB file imported successfully\r\n");
435
    } else {
436
        mg_printf(conn, "WARN: %s\r\n", warnings);
437
        g_free(warnings);
438
    }
439

                
440
out:
441
    free(collection_name);
442
    free(nzb_data);
443
    return;
444
}
445

                
446
static void
447
on_log_message_received(struct mg_connection *conn, const struct mg_request_info *info, void *message)
448
{
449
    g_return_if_fail(plugin_data_global != NULL);
450

                
451
    ng_plugin_emit_log_msg(plugin_data_global, NG_LOG_LEVEL_INFO, "%s", (char*) message);
452
}