Browse code

jsonrpc-s: new module implementing a JSONRPC server over HTTP

- it uses internal json library
- requires xhttp module to handle http messages

Daniel-Constantin Mierla authored on 14/08/2014 07:13:57
Showing 7 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,12 @@
1
+# 
2
+# WARNING: do not run this directly, it should be run by the master Makefile
3
+
4
+include ../../Makefile.defs
5
+auto_gen=
6
+NAME=jsonrpc-s.so
7
+DEFS +=
8
+LIBS +=
9
+
10
+DEFS+=-DKAMAILIO_MOD_INTERFACE
11
+
12
+include ../../Makefile.modules
0 13
new file mode 100644
... ...
@@ -0,0 +1,162 @@
1
+JSONRPC-S Module
2
+
3
+Daniel-Constantin Mierla
4
+
5
+   <miconda@gmail.com>
6
+
7
+Edited by
8
+
9
+Daniel-Constantin Mierla
10
+
11
+   <miconda@gmail.com>
12
+
13
+   Copyright � 2014 asipto.com
14
+     __________________________________________________________________
15
+
16
+   Table of Contents
17
+
18
+   1. Admin Guide
19
+
20
+        1. Overview
21
+
22
+              1.1. Limitations
23
+
24
+        2. Dependencies
25
+
26
+              2.1. Kamailio Modules
27
+              2.2. External Libraries or Applications
28
+
29
+        3. Parameters
30
+
31
+              3.1. pretty_format (int)
32
+
33
+        4. Functions
34
+
35
+              4.1. jsonrpc_dispatch()
36
+
37
+   List of Examples
38
+
39
+   1.1. Set pretty_format parameter
40
+   1.2. jsonrpc_dispatch usage
41
+
42
+Chapter 1. Admin Guide
43
+
44
+   Table of Contents
45
+
46
+   1. Overview
47
+
48
+        1.1. Limitations
49
+
50
+   2. Dependencies
51
+
52
+        2.1. Kamailio Modules
53
+        2.2. External Libraries or Applications
54
+
55
+   3. Parameters
56
+
57
+        3.1. pretty_format (int)
58
+
59
+   4. Functions
60
+
61
+        4.1. jsonrpc_dispatch()
62
+
63
+1. Overview
64
+
65
+   1.1. Limitations
66
+
67
+   This module provides JSONRPC server over HTTP implementation, tailored
68
+   for the needs of Kamailio.
69
+
70
+   The JSONRPC-S module uses the xHTTP module to handle HTTP requests.
71
+   Read the documentation of the xHTTP module for more details.
72
+
73
+1.1. Limitations
74
+
75
+     * This module does not implement asynchronous RPC commands. It is
76
+       unlikely that asynchronous RPC commands will be executed from an
77
+       JSONRPC over HTTP client.
78
+     * This module does not accept parameters embedded in a structure (see
79
+       RPC documentation for more info about how parameters can be passed
80
+       to RPC).
81
+
82
+2. Dependencies
83
+
84
+   2.1. Kamailio Modules
85
+   2.2. External Libraries or Applications
86
+
87
+2.1. Kamailio Modules
88
+
89
+   The following modules must be loaded before this module:
90
+     * xhttp - xHTTP.
91
+
92
+2.2. External Libraries or Applications
93
+
94
+   The following libraries or applications must be installed before
95
+   running Kamailio with this module loaded:
96
+     * None
97
+
98
+3. Parameters
99
+
100
+   3.1. pretty_format (int)
101
+
102
+3.1. pretty_format (int)
103
+
104
+   Pretty format for JSONRPC response document.
105
+
106
+   Default value is '0'.
107
+
108
+   Example 1.1. Set pretty_format parameter
109
+...
110
+modparam("jsonrpc-s", "pretty_format", 1)
111
+...
112
+
113
+4. Functions
114
+
115
+   4.1. jsonrpc_dispatch()
116
+
117
+4.1. jsonrpc_dispatch()
118
+
119
+   Handle the JSONRPC request and generate a response.
120
+
121
+   Example 1.2. jsonrpc_dispatch usage
122
+...
123
+#!KAMAILIO
124
+
125
+memdbg=5
126
+memlog=5
127
+
128
+debug=3
129
+log_stderror=yes
130
+
131
+fork=yes
132
+children=2
133
+
134
+tcp_accept_no_cl=yes
135
+
136
+mpath="modules/"
137
+
138
+loadmodule "sl.so"
139
+loadmodule "pv.so"
140
+loadmodule "xhttp.so"
141
+loadmodule "jsonrpc-s.so"
142
+
143
+request_route {
144
+        send_reply("404", "not found");
145
+        exit;
146
+}
147
+
148
+event_route[xhttp:request] {
149
+    if(src_ip!=127.0.0.1) {
150
+        xhttp_reply("403", "Forbidden", "text/html",
151
+            "<html><body>Not allowed from $si</body></html>");
152
+        exit;
153
+        }
154
+        if ($hu =~ "^/RPC") {
155
+                jsonrpc_dispatch();
156
+        } else {
157
+        xhttp_reply("200", "OK", "text/html",
158
+            "<html><body>Wrong URL $hu</body></html>");
159
+    }
160
+    return;
161
+}
162
+...
0 163
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+docs = jsonrpc-s.xml
2
+
3
+docbook_dir = ../../../docbook
4
+include $(docbook_dir)/Makefile.module
0 5
new file mode 100644
... ...
@@ -0,0 +1,37 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+
11
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
12
+    <bookinfo>
13
+	<title>JSONRPC-S Module</title>
14
+	<productname class="trade">kamailio.org</productname>
15
+	<authorgroup>
16
+	    <author>
17
+		<firstname>Daniel-Constantin</firstname>
18
+		<surname>Mierla</surname>
19
+		<email>miconda@gmail.com</email>
20
+	    </author>
21
+	    <editor>
22
+		<firstname>Daniel-Constantin</firstname>
23
+		<surname>Mierla</surname>
24
+		<email>miconda@gmail.com</email>
25
+	    </editor>
26
+	</authorgroup>
27
+	<copyright>
28
+	    <year>2014</year>
29
+	    <holder>asipto.com</holder>
30
+	</copyright>
31
+    </bookinfo>
32
+    <toc></toc>
33
+    
34
+    <xi:include href="jsonrpc-s_admin.xml"/>
35
+    
36
+    
37
+</book>
0 38
new file mode 100644
... ...
@@ -0,0 +1,159 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+<!-- Module User's Guide -->
11
+
12
+<chapter>
13
+	
14
+	<title>&adminguide;</title>
15
+	
16
+	<section>
17
+	<title>Overview</title>
18
+	<para>
19
+		This module provides JSONRPC server over HTTP implementation,
20
+		tailored for the needs of &kamailio;.
21
+	<para>
22
+	</para>
23
+		The JSONRPC-S module uses the xHTTP module to handle HTTP requests.
24
+		Read the documentation of the xHTTP module for more details.
25
+	</para>
26
+
27
+	<section>
28
+	<title>Limitations</title>
29
+	<itemizedlist>
30
+	<listitem>
31
+	<para>
32
+		This module does not implement asynchronous RPC commands.
33
+		It is unlikely that asynchronous RPC commands will be executed
34
+		from an JSONRPC over HTTP client.
35
+	</para>
36
+	</listitem>
37
+	<listitem>
38
+	<para>
39
+		This module does not accept parameters embedded in a structure
40
+		(see RPC documentation for more info about how parameters can be
41
+		passed to RPC).
42
+	</para>
43
+	</listitem>
44
+	</itemizedlist>
45
+	</section>
46
+	</section>
47
+
48
+	<section>
49
+	<title>Dependencies</title>
50
+	<section>
51
+		<title>&kamailio; Modules</title>
52
+		<para>
53
+		The following modules must be loaded before this module:
54
+			<itemizedlist>
55
+			<listitem>
56
+			<para>
57
+				<emphasis>xhttp</emphasis> - xHTTP.
58
+			</para>
59
+			</listitem>
60
+			</itemizedlist>
61
+		</para>
62
+	</section>
63
+	<section>
64
+		<title>External Libraries or Applications</title>
65
+		<para>
66
+		The following libraries or applications must be installed before running
67
+		&kamailio; with this module loaded:
68
+			<itemizedlist>
69
+			<listitem>
70
+			<para>
71
+				<emphasis>None</emphasis>
72
+			</para>
73
+			</listitem>
74
+			</itemizedlist>
75
+		</para>
76
+	</section>
77
+	</section>
78
+	<section>
79
+	<title>Parameters</title>
80
+	<section>
81
+		<title><varname>pretty_format</varname> (int)</title>
82
+		<para>
83
+			Pretty format for JSONRPC response document. 
84
+		</para>
85
+		<para>
86
+		<emphasis>
87
+			Default value is '0'.
88
+		</emphasis>
89
+		</para>
90
+		<example>
91
+		<title>Set <varname>pretty_format</varname> parameter</title>
92
+		<programlisting format="linespecific">
93
+...
94
+modparam("jsonrpc-s", "pretty_format", 1)
95
+...
96
+</programlisting>
97
+		</example>
98
+	</section>
99
+	</section>
100
+
101
+	<section>
102
+	<title>Functions</title>
103
+ 	<section>
104
+	    <title>
105
+		<function moreinfo="none">jsonrpc_dispatch()</function>
106
+	    </title>
107
+	    <para>
108
+		Handle the JSONRPC request and generate a response.
109
+	    </para>
110
+		<example>
111
+		<title><function>jsonrpc_dispatch</function> usage</title>
112
+		<programlisting format="linespecific">
113
+...
114
+#!KAMAILIO
115
+
116
+memdbg=5
117
+memlog=5
118
+
119
+debug=3
120
+log_stderror=yes
121
+
122
+fork=yes
123
+children=2
124
+
125
+tcp_accept_no_cl=yes
126
+
127
+mpath="modules/"
128
+
129
+loadmodule "sl.so"
130
+loadmodule "pv.so"
131
+loadmodule "xhttp.so"
132
+loadmodule "jsonrpc-s.so"
133
+
134
+request_route {
135
+	send_reply("404", "not found");
136
+	exit;
137
+}
138
+
139
+event_route[xhttp:request] {
140
+    if(src_ip!=127.0.0.1) {
141
+        xhttp_reply("403", "Forbidden", "text/html",
142
+            "&lt;html&gt;&lt;body&gt;Not allowed from $si&lt;/body&gt;&lt;/html&gt;");
143
+        exit;
144
+	}
145
+	if ($hu =~ "^/RPC") {
146
+		jsonrpc_dispatch();
147
+	} else {
148
+        xhttp_reply("200", "OK", "text/html",
149
+            "&lt;html&gt;&lt;body&gt;Wrong URL $hu&lt;/body&gt;&lt;/html&gt;");
150
+    }
151
+    return;
152
+}
153
+...
154
+</programlisting>
155
+	    </example>
156
+	</section>
157
+	</section>
158
+</chapter>
159
+
0 160
new file mode 100644
... ...
@@ -0,0 +1,846 @@
1
+/**
2
+ * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio 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
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ *
20
+ */
21
+
22
+#include <string.h>
23
+#include <stdlib.h>
24
+#include <stdio.h>
25
+#include <stdarg.h>
26
+
27
+#include "../../ver.h"
28
+#include "../../trim.h"
29
+#include "../../sr_module.h"
30
+#include "../../nonsip_hooks.h"
31
+#include "../../modules/xhttp/api.h"
32
+
33
+#include "jsonrpc-s_mod.h"
34
+
35
+/** @addtogroup jsonrpc-s
36
+ * @ingroup modules
37
+ * @{
38
+ *
39
+ * <h1>Overview of Operation</h1>
40
+ * This module provides jsonrpc over http server implementation.
41
+ */
42
+
43
+/** @file
44
+ *
45
+ * This is the main file of jsonrpc-s module which contains all the functions
46
+ * related to http processing, as well as the module interface.
47
+ */
48
+
49
+MODULE_VERSION
50
+
51
+
52
+#define jsonrpc_malloc	pkg_malloc
53
+#define jsonrpc_free	pkg_free
54
+
55
+str JSONRPC_REASON_OK = str_init("OK");
56
+str JSONRPC_CONTENT_TYPE_HTML = str_init("application/json");
57
+
58
+static int jsonrpc_pretty_format = 0;
59
+
60
+static int jsonrpc_register_rpc(void);
61
+
62
+static int mod_init(void);
63
+static int child_init(int rank);
64
+static int jsonrpc_dispatch(sip_msg_t* msg, char* s1, char* s2);
65
+
66
+
67
+/** The context of the jsonrpc request being processed.
68
+ *
69
+ * This is a global variable that records the context of the jsonrpc request
70
+ * being currently processed.
71
+ * @sa rpc_ctx
72
+ */
73
+static jsonrpc_ctx_t _jsonrpc_ctx;
74
+
75
+static xhttp_api_t xhttp_api;
76
+
77
+/** Pointers to the functions that implement the RPC interface
78
+ * of jsonrpc module
79
+ */
80
+static rpc_t func_param;
81
+
82
+#define JSONRPC_ERROR_REASON_BUF_LEN	128
83
+#define JSONRPC_PRINT_VALUE_BUF_LEN		1024
84
+
85
+char jsonrpc_error_buf[JSONRPC_ERROR_REASON_BUF_LEN];
86
+
87
+static cmd_export_t cmds[] = {
88
+	{"jsonrpc_dispatch", (cmd_function)jsonrpc_dispatch, 0, 0, 0, REQUEST_ROUTE},
89
+	{0, 0, 0, 0, 0, 0}
90
+};
91
+
92
+static param_export_t params[] = {
93
+	{"pretty_format",   PARAM_INT,    &jsonrpc_pretty_format},
94
+	{0, 0, 0}
95
+};
96
+
97
+/** module exports */
98
+struct module_exports exports= {
99
+	"jsonrpc-s",
100
+	DEFAULT_DLFLAGS, /* dlopen flags */
101
+	cmds,
102
+	params,
103
+	0,		/* exported statistics */
104
+	0,		/* exported MI functions */
105
+	0,		/* exported pseudo-variables */
106
+	0,		/* extra processes */
107
+	mod_init,	/* module initialization function */
108
+	0,
109
+	0,
110
+	child_init	/* per-child init function */
111
+};
112
+
113
+
114
+typedef struct jsonrpc_error {
115
+	int code;
116
+	str text;
117
+} jsonrpc_error_t;
118
+
119
+static jsonrpc_error_t _jsonrpc_error_table[] = {
120
+	{ -32700, { "Parse Error", 11 } },
121
+	{ -32600, { "Invalid Request", 15 } },
122
+	{ -32601, { "Method Not Found", 16 } },
123
+	{ -32602, { "Invalid Parameters", 18 } },
124
+	{ -32603, { "Internal Error", 14 } },
125
+	{ -32000, { "Execution Error", 15 } },
126
+	{0, { 0, 0 } } 
127
+};
128
+
129
+/** Implementation of rpc_fault function required by the management API.
130
+ *
131
+ * This function will be called whenever a management function
132
+ * indicates that an error ocurred while it was processing the request. The
133
+ * function takes the reply code and reason phrase as parameters, these will
134
+ * be put in the body of the reply.
135
+ *
136
+ * @param ctx A pointer to the context structure of the request being
137
+ *            processed.
138
+ * @param code Reason code.
139
+ * @param fmt Formatting string used to build the reason phrase.
140
+ */
141
+static void jsonrpc_fault(jsonrpc_ctx_t* ctx, int code, char* fmt, ...)
142
+{
143
+	va_list ap;
144
+
145
+	ctx->http_code = code;
146
+	va_start(ap, fmt);
147
+	vsnprintf(jsonrpc_error_buf, JSONRPC_ERROR_REASON_BUF_LEN, fmt, ap);
148
+	va_end(ap);
149
+	ctx->http_text.len = strlen(jsonrpc_error_buf);
150
+	ctx->http_text.s = jsonrpc_error_buf;
151
+	if(ctx->error_code == 0) ctx->error_code = -32000;
152
+
153
+	return;
154
+}
155
+
156
+
157
+
158
+/** Initialize jsonrpc reply data structure.
159
+ *
160
+ * This function initializes the data structure that contains all data related
161
+ * to the jsonrpc reply being created. The function must be called before any
162
+ * other function that adds data to the reply.
163
+ * @param ctx jsonrpc_ctx_t structure to be initialized.
164
+ * @return 0 on success, a negative number on error.
165
+ */
166
+static int jsonrpc_init_reply(jsonrpc_ctx_t *ctx)
167
+{
168
+	srjson_t *nj = NULL;
169
+
170
+	ctx->http_code = 200;
171
+	ctx->http_text = JSONRPC_REASON_OK;
172
+	ctx->jrpl = srjson_NewDoc(NULL);
173
+	if(ctx->jrpl==NULL) {
174
+		LM_ERR("Failed to init the reply json document\n");
175
+		return -1;
176
+	}
177
+	ctx->jrpl->root = srjson_CreateObject(ctx->jrpl);
178
+	if(ctx->jrpl->root==NULL) {
179
+		LM_ERR("Failed to init the reply json root node\n");
180
+		return -1;
181
+	}
182
+	srjson_AddStrStrToObject(ctx->jrpl, ctx->jrpl->root,
183
+					"jsonrpc", 7,
184
+					"2.0", 3);
185
+
186
+	return 0;
187
+}
188
+
189
+/** Implementation of rpc_send function required by the management API.
190
+ *
191
+ * This is the function that will be called whenever a management function
192
+ * asks the management interface to send the reply to the client.
193
+ * The SIP/HTTP reply sent to
194
+ * the client will be always 200 OK, if an error ocurred on the server then it
195
+ * will be indicated in the html document in body.
196
+ *
197
+ * @param ctx A pointer to the context structure of the jsonrpc request that
198
+ *            generated the reply.
199
+ * @return 1 if the reply was already sent, 0 on success, a negative number on
200
+ *            error
201
+ */
202
+static int jsonrpc_send(jsonrpc_ctx_t* ctx)
203
+{
204
+	srjson_t *nj = NULL;
205
+	int i;
206
+	str rbuf;
207
+
208
+	if (ctx->reply_sent) return 1;
209
+
210
+	ctx->reply_sent = 1;
211
+
212
+	if(ctx->error_code != 0) {
213
+		/* fault handling */
214
+		nj = srjson_CreateObject(ctx->jrpl);
215
+		if(nj!=NULL) {
216
+			srjson_AddNumberToObject(ctx->jrpl, nj, "code",
217
+					ctx->error_code);
218
+			for(i=0; _jsonrpc_error_table[i].code!=0
219
+					&& _jsonrpc_error_table[i].code!=ctx->error_code; i++);
220
+			if(_jsonrpc_error_table[i].code!=0) {
221
+				srjson_AddStrStrToObject(ctx->jrpl, nj,
222
+					"message", 7,
223
+					_jsonrpc_error_table[i].text.s,
224
+					_jsonrpc_error_table[i].text.len);
225
+			} else {
226
+				srjson_AddStrStrToObject(ctx->jrpl, nj,
227
+					"message", 7, "Unexpected Error", 16);
228
+			}
229
+			srjson_AddItemToObject(ctx->jrpl, ctx->jrpl->root, "error", nj);
230
+		}
231
+	} else {
232
+		nj = srjson_GetObjectItem(ctx->jrpl, ctx->jrpl->root, "result");
233
+		if(nj==NULL) {
234
+			if(ctx->rpl_node!=NULL) {
235
+				srjson_AddItemToObject(ctx->jrpl, ctx->jrpl->root,
236
+					"result", ctx->rpl_node);
237
+				ctx->rpl_node = 0;
238
+			} else {
239
+				srjson_AddStrStrToObject(ctx->jrpl, ctx->jrpl->root,
240
+					"result", 6, "ok", 2);
241
+			}
242
+		}
243
+	}
244
+	nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "id");
245
+	if(nj!=NULL) {
246
+		if(nj->valuestring!=NULL) {
247
+			srjson_AddStrStrToObject(ctx->jrpl, ctx->jrpl->root,
248
+					"id", 2,
249
+					nj->valuestring, strlen(nj->valuestring));
250
+		} else {
251
+			srjson_AddNumberToObject(ctx->jrpl, ctx->jrpl->root, "id",
252
+					nj->valueint);
253
+		}
254
+	}
255
+
256
+	if(jsonrpc_pretty_format==0) {
257
+		rbuf.s = srjson_PrintUnformatted(ctx->jrpl, ctx->jrpl->root);
258
+	} else {
259
+		rbuf.s = srjson_Print(ctx->jrpl, ctx->jrpl->root);
260
+	}
261
+	if(rbuf.s!=NULL) {
262
+		rbuf.len = strlen(rbuf.s);
263
+	}
264
+	if (rbuf.s!=NULL) {
265
+		xhttp_api.reply(ctx->msg, ctx->http_code, &ctx->http_text,
266
+			&JSONRPC_CONTENT_TYPE_HTML, &rbuf);
267
+	} else {
268
+		xhttp_api.reply(ctx->msg, ctx->http_code, &ctx->http_text,
269
+				NULL, NULL);
270
+	}
271
+	if (rbuf.s!=NULL) {
272
+		ctx->jrpl->free_fn(rbuf.s);
273
+	}
274
+
275
+	return 0;
276
+}
277
+
278
+
279
+/** Converts the variables provided in parameter ap according to formatting
280
+ * string provided in parameter fmt into HTML format.
281
+ *
282
+ * This function takes the parameters provided in ap parameter and creates
283
+ * HTML formatted parameters that will be put in the html document.
284
+ * The format of input parameters is described in formatting string
285
+ * fmt which follows the syntax of the management API. In the case of
286
+ * an error the function will generate an error reply in err_reply parameter
287
+ * instead.
288
+ * @param ctx An error reply document will be generated here if the
289
+ *                  function encounters a problem while processing input
290
+ *                  parameters.
291
+ * @param fmt Formatting string of the management API.
292
+ * @param ap A pointer to the array of input parameters.
293
+ *
294
+ */
295
+static srjson_t* jsonrpc_print_value(jsonrpc_ctx_t* ctx, char fmt, va_list* ap)
296
+
297
+{
298
+	srjson_t *nj = NULL;
299
+	char buf[JSONRPC_PRINT_VALUE_BUF_LEN];
300
+	time_t dt;
301
+	struct tm* t;
302
+	str *sp;
303
+
304
+	switch(fmt) {
305
+	case 'd':
306
+		nj = srjson_CreateNumber(ctx->jrpl, va_arg(*ap, int));
307
+		break;
308
+	case 'f':
309
+		nj = srjson_CreateNumber(ctx->jrpl, va_arg(*ap, double));
310
+		break;
311
+	case 'b':
312
+		nj = srjson_CreateBool(ctx->jrpl, ((va_arg(*ap, int)==0)?0:1));
313
+		break;
314
+	case 't':
315
+		dt = va_arg(*ap, time_t);
316
+		t = gmtime(&dt);
317
+		if (strftime(buf, JSONRPC_PRINT_VALUE_BUF_LEN,
318
+				"%Y%m%dT%H:%M:%S", t) == 0) {
319
+			LM_ERR("Error while converting time\n");
320
+			return NULL;
321
+		}
322
+		nj = srjson_CreateString(ctx->jrpl, buf);
323
+		break;
324
+	case 's':
325
+		nj = srjson_CreateString(ctx->jrpl, va_arg(*ap, char*));
326
+		break;
327
+	case 'S':
328
+		sp = va_arg(*ap, str*);
329
+		nj = srjson_CreateStr(ctx->jrpl, sp->s, sp->len);
330
+		break;
331
+	default:
332
+		LM_ERR("Invalid formatting character [%c]\n", fmt);
333
+		return NULL;
334
+	}
335
+	return nj;
336
+}
337
+
338
+
339
+
340
+/** Implementation of rpc_add function required by the management API.
341
+ *
342
+ * This function will be called when an RPC management function calls
343
+ * rpc->add to add a parameter to the jsonrpc reply being generated.
344
+ */
345
+static int jsonrpc_add(jsonrpc_ctx_t* ctx, char* fmt, ...)
346
+{
347
+	srjson_t *nj = NULL;
348
+	void **void_ptr;
349
+	va_list ap;
350
+
351
+	va_start(ap, fmt);
352
+	while(*fmt) {
353
+		if (*fmt == '{' || *fmt == '[') {
354
+			void_ptr = va_arg(ap, void**);
355
+			if (*fmt == '{') {
356
+				nj = srjson_CreateObject(ctx->jrpl);
357
+			} else {
358
+				nj = srjson_CreateArray(ctx->jrpl);
359
+			}
360
+			*void_ptr = nj;
361
+		} else {
362
+			nj = jsonrpc_print_value(ctx, *fmt, &ap);
363
+		}
364
+
365
+		if(nj==NULL) goto err;
366
+		if(ctx->flags & RET_ARRAY) {
367
+			if (ctx->rpl_node==NULL) {
368
+				ctx->rpl_node = srjson_CreateArray(ctx->jrpl);
369
+				if(ctx->rpl_node == 0) {
370
+					LM_ERR("failed to create the root array node\n");
371
+					goto err;
372
+				}
373
+			}
374
+			srjson_AddItemToArray(ctx->jrpl, ctx->rpl_node, nj);
375
+		} else {
376
+			if (ctx->rpl_node) srjson_Delete(ctx->jrpl, ctx->rpl_node);
377
+			ctx->rpl_node = nj;
378
+		}
379
+
380
+		fmt++;
381
+	}
382
+	va_end(ap);
383
+	return 0;
384
+err:
385
+	va_end(ap);
386
+	return -1;
387
+}
388
+
389
+
390
+/** Implementation of rpc->scan function required by the management API.
391
+ *
392
+ * This is the function that will be called whenever a management function
393
+ * calls rpc->scan to get the value of parameter from the jsonrpc
394
+ * request. This function will extract the current parameter from the jsonrpc
395
+ * URL and attempts to convert it to the type requested by the management
396
+ * function that called it.
397
+ */
398
+static int jsonrpc_scan(jsonrpc_ctx_t* ctx, char* fmt, ...)
399
+{
400
+	int *int_ptr;
401
+	char **char_ptr;
402
+	double *double_ptr;
403
+	str *str_ptr;
404
+
405
+	str arg;
406
+
407
+	int mandatory_param = 1;
408
+	int modifiers = 0;
409
+	int auto_convert = 0;
410
+	char* orig_fmt;
411
+	va_list ap;
412
+
413
+	if(ctx->req_node==NULL)
414
+		return 0;
415
+
416
+	orig_fmt=fmt;
417
+	va_start(ap, fmt);
418
+	while(*fmt && ctx->req_node) {
419
+		switch(*fmt) {
420
+		case '*': /* start of optional parameters */
421
+			mandatory_param = 0;
422
+			modifiers++;
423
+			fmt++;
424
+			continue;
425
+		case '.': /* autoconvert */
426
+			modifiers++;
427
+			fmt++;
428
+			auto_convert = 1;
429
+			continue;
430
+		case 'b': /* Bool */
431
+		case 't': /* Date and time */
432
+		case 'd': /* Integer */
433
+			int_ptr = va_arg(ap, int*);
434
+			*int_ptr = ctx->req_node->valueint;
435
+			break;
436
+		case 'f': /* double */
437
+			double_ptr = va_arg(ap, double*);
438
+			*double_ptr = ctx->req_node->valuedouble;
439
+			break;
440
+		case 's': /* zero terminated string */
441
+			char_ptr = va_arg(ap, char**);
442
+			*char_ptr = ctx->req_node->valuestring;
443
+			break;
444
+		case 'S': /* str structure */
445
+			str_ptr = va_arg(ap, str*);
446
+			str_ptr->s = ctx->req_node->valuestring;
447
+			str_ptr->len = strlen(ctx->req_node->valuestring);
448
+			break;
449
+		case '{':
450
+		case '[':
451
+			LM_ERR("Unsupported param type '%c'\n", *fmt);
452
+			jsonrpc_fault(ctx, 500, "Unsupported param type");
453
+			goto error;
454
+		default:
455
+			LM_ERR("Invalid param type in formatting string: [%c]\n", *fmt);
456
+			jsonrpc_fault(ctx, 500,
457
+				"Internal Server Error (inval formatting str)");
458
+			goto error;
459
+		}
460
+		fmt++;
461
+		auto_convert = 0;
462
+		ctx->req_node = ctx->req_node->next;
463
+	}
464
+	va_end(ap);
465
+	return (int)(fmt-orig_fmt)-modifiers;
466
+error:
467
+	va_end(ap);
468
+	return -((int)(fmt-orig_fmt)-modifiers);
469
+}
470
+
471
+
472
+/** Implementation of rpc_printf function required by the management API.
473
+ *
474
+ * This function will be called whenever an RPC management function calls
475
+ * rpc-printf to add a parameter to the jsonrpc reply being constructed.
476
+ */
477
+static int jsonrpc_printf(jsonrpc_ctx_t* ctx, char* fmt, ...)
478
+{
479
+	int n, buf_size;
480
+	char *buf = 0;
481
+	char tbuf[JSONRPC_PRINT_VALUE_BUF_LEN];
482
+	va_list ap;
483
+	srjson_t *nj = NULL;
484
+
485
+	buf = tbuf;
486
+	buf_size = JSONRPC_PRINT_VALUE_BUF_LEN;
487
+	while (1) {
488
+		/* try to print in the allocated space. */
489
+		va_start(ap, fmt);
490
+		n = vsnprintf(buf, buf_size, fmt, ap);
491
+		va_end(ap);
492
+		/* if that worked, return the string. */
493
+		if (n > -1 && n < buf_size) {
494
+			nj = srjson_CreateString(ctx->jrpl, buf);
495
+			if(buf && buf!=tbuf) jsonrpc_free(buf);
496
+			return 0;
497
+		}
498
+		/* else try again with more space. */
499
+		if (n > -1) {   /* glibc 2.1 */
500
+			buf_size = n + 1; /* precisely what is needed */
501
+		} else {          /* glibc 2.0 */
502
+			buf_size *= 2;  /* twice the old size */
503
+		}
504
+		if(buf && buf!=tbuf) jsonrpc_free(buf);
505
+		if ((buf = jsonrpc_malloc(buf_size)) == 0) {
506
+			jsonrpc_fault(ctx, 500, "Internal Server Error (No memory left)");
507
+			LM_ERR("no memory left for rpc printf\n");
508
+			return -1;
509
+		}
510
+	}
511
+}
512
+
513
+
514
+/** Adds a new member to structure.
515
+ */
516
+static int jsonrpc_struct_add(srjson_t *jnode, char* fmt, ...)
517
+{
518
+	srjson_t *nj = NULL;
519
+	jsonrpc_ctx_t* ctx;
520
+	va_list ap;
521
+	void **void_ptr;
522
+	str mname;
523
+
524
+	if(jnode==NULL) {
525
+		LM_ERR("invalid json node parameter\n");
526
+		return -1;
527
+	}
528
+	if(jnode->type!=srjson_Object) {
529
+		LM_ERR("json node parameter is not object (%d)\n", jnode->type);
530
+		return -1;
531
+	}
532
+
533
+	ctx = &_jsonrpc_ctx;
534
+	if(ctx->jrpl==NULL) {
535
+		LM_ERR("reply object not initialized in rpl context\n");
536
+		return -1;
537
+	}
538
+
539
+	va_start(ap, fmt);
540
+	while(*fmt) {
541
+		mname.s = va_arg(ap, char*);
542
+		mname.len = (mname.s?strlen(mname.s):0);
543
+
544
+		if (*fmt == '{' || *fmt == '[') {
545
+			void_ptr = va_arg(ap, void**);
546
+			if (*fmt == '{') {
547
+				nj = srjson_CreateObject(ctx->jrpl);
548
+			} else {
549
+				nj = srjson_CreateArray(ctx->jrpl);
550
+			}
551
+			*void_ptr = nj;
552
+		} else {
553
+			nj = jsonrpc_print_value(ctx, *fmt, &ap);
554
+		}
555
+
556
+		if(nj==NULL) goto err;
557
+		srjson_AddItemToObject(ctx->jrpl, jnode,
558
+					mname.s, nj);
559
+		fmt++;
560
+	}
561
+	va_end(ap);
562
+	return 0;
563
+err:
564
+	va_end(ap);
565
+	return -1;
566
+}
567
+
568
+
569
+/** Adds a new member to structure.
570
+ */
571
+static int jsonrpc_array_add(srjson_t *jnode, char* fmt, ...)
572
+{
573
+	srjson_t *nj = NULL;
574
+	jsonrpc_ctx_t* ctx;
575
+	va_list ap;
576
+	void **void_ptr;
577
+
578
+	if(jnode==NULL) {
579
+		LM_ERR("invalid json node parameter\n");
580
+		return -1;
581
+	}
582
+	if(jnode->type!=srjson_Array) {
583
+		LM_ERR("json node parameter is not array (%d)\n", jnode->type);
584
+		return -1;
585
+	}
586
+
587
+	ctx = &_jsonrpc_ctx;
588
+	if(ctx->jrpl==NULL) {
589
+		LM_ERR("reply object not initialized in rpl context\n");
590
+		return -1;
591
+	}
592
+
593
+	va_start(ap, fmt);
594
+	while(*fmt) {
595
+		if (*fmt == '{' || *fmt == '[') {
596
+			void_ptr = va_arg(ap, void**);
597
+			if (*fmt == '{') {
598
+				nj = srjson_CreateObject(ctx->jrpl);
599
+			} else {
600
+				nj = srjson_CreateArray(ctx->jrpl);
601
+			}
602
+			*void_ptr = nj;
603
+		} else {
604
+			nj = jsonrpc_print_value(ctx, *fmt, &ap);
605
+		}
606
+
607
+		if(nj==NULL) goto err;
608
+		srjson_AddItemToArray(ctx->jrpl, jnode, nj);
609
+		fmt++;
610
+	}
611
+	va_end(ap);
612
+	return 0;
613
+err:
614
+	va_end(ap);
615
+	return -1;
616
+}
617
+
618
+
619
+static int jsonrpc_struct_scan(void* s, char* fmt, ...)
620
+{
621
+	LM_ERR("Not implemented\n");
622
+	return -1;
623
+}
624
+
625
+
626
+/** Create a new member from formatting string and add it to a structure.
627
+ */
628
+static int jsonrpc_struct_printf(void* s, char* member_name, char* fmt, ...)
629
+{
630
+	LM_ERR("Not implemented\n");
631
+	return -1;
632
+}
633
+
634
+
635
+/** Returns the RPC capabilities supported by the xmlrpc driver.
636
+ */
637
+static rpc_capabilities_t jsonrpc_capabilities(jsonrpc_ctx_t* ctx)
638
+{
639
+	/* No support for async commands.
640
+	 */
641
+	return 0;
642
+}
643
+
644
+
645
+/** Returns a new "delayed reply" context.
646
+ * Creates a new delayed reply context in shm and returns it.
647
+ * @return 0 - not supported, already replied, or no more memory;
648
+ *         !=0 pointer to the special delayed ctx.
649
+ * Note1: one should use the returned ctx reply context to build a reply and
650
+ *  when finished call rpc_delayed_ctx_close().
651
+ * Note2: adding pieces to the reply in different processes is not supported.
652
+ */
653
+static struct rpc_delayed_ctx* jsonrpc_delayed_ctx_new(jsonrpc_ctx_t* ctx)
654
+{
655
+	return NULL;
656
+}
657
+
658
+
659
+/** Closes a "delayed reply" context and sends the reply.
660
+ * If no reply has been sent the reply will be built and sent automatically.
661
+ * See the notes from rpc_new_delayed_ctx()
662
+ */
663
+static void jsonrpc_delayed_ctx_close(struct rpc_delayed_ctx* dctx)
664
+{
665
+	return;
666
+}
667
+
668
+
669
+static void jsonrpc_clean_context(jsonrpc_ctx_t* ctx)
670
+{
671
+	if (!ctx) return;
672
+	srjson_DeleteDoc(ctx->jreq);
673
+	if(ctx->rpl_node!=NULL) {
674
+		srjson_Delete(ctx->jrpl, ctx->rpl_node);
675
+		ctx->rpl_node = NULL;
676
+	}
677
+	srjson_DeleteDoc(ctx->jrpl);
678
+}
679
+
680
+static int mod_init(void)
681
+{
682
+	int i;
683
+
684
+	/* bind the XHTTP API */
685
+	if (xhttp_load_api(&xhttp_api) < 0) {
686
+		LM_ERR("cannot bind to XHTTP API\n");
687
+		return -1;
688
+	}
689
+
690
+	memset(&func_param, 0, sizeof(func_param));
691
+	func_param.send              = (rpc_send_f)jsonrpc_send;
692
+	func_param.fault             = (rpc_fault_f)jsonrpc_fault;
693
+	func_param.add               = (rpc_add_f)jsonrpc_add;
694
+	func_param.scan              = (rpc_scan_f)jsonrpc_scan;
695
+	func_param.printf            = (rpc_printf_f)jsonrpc_printf;
696
+	func_param.struct_add        = (rpc_struct_add_f)jsonrpc_struct_add;
697
+	func_param.array_add         = (rpc_struct_add_f)jsonrpc_array_add;
698
+	func_param.struct_scan       = (rpc_struct_scan_f)jsonrpc_struct_scan;
699
+	func_param.struct_printf     = (rpc_struct_printf_f)jsonrpc_struct_printf;
700
+	func_param.capabilities      = (rpc_capabilities_f)jsonrpc_capabilities;
701
+	func_param.delayed_ctx_new   = (rpc_delayed_ctx_new_f)jsonrpc_delayed_ctx_new;
702
+	func_param.delayed_ctx_close =
703
+		(rpc_delayed_ctx_close_f)jsonrpc_delayed_ctx_close;
704
+
705
+	jsonrpc_register_rpc();
706
+
707
+	return 0;
708
+}
709
+
710
+static int child_init(int rank)
711
+{
712
+	if(rank==PROC_MAIN || rank==PROC_TCP_MAIN)
713
+		return 0; /* do nothing for the main process */
714
+
715
+	return 0;
716
+}
717
+
718
+
719
+static int jsonrpc_dispatch(sip_msg_t* msg, char* s1, char* s2)
720
+{
721
+	rpc_export_t* rpce;
722
+	jsonrpc_ctx_t* ctx;
723
+	str arg = {NULL, 0};
724
+	int ret = 0;
725
+	int i;
726
+	srjson_t *nj = NULL;
727
+	str val;
728
+
729
+	if(!IS_HTTP(msg)) {
730
+		LM_DBG("Got non HTTP msg\n");
731
+		return NONSIP_MSG_PASS;
732
+	}
733
+
734
+	/* initialize jsonrpc context */
735
+	ctx = &_jsonrpc_ctx;
736
+	memset(ctx, 0, sizeof(jsonrpc_ctx_t));
737
+	ctx->msg = msg;
738
+	/* parse the jsonrpc request */
739
+	ctx->jreq = srjson_NewDoc(NULL);
740
+	if(ctx->jreq==NULL) {
741
+		LM_ERR("Failed to init the json document\n");
742
+		return NONSIP_MSG_PASS;
743
+	}
744
+
745
+	ctx->jreq->buf.s = get_body(msg);
746
+	ctx->jreq->buf.len = strlen(ctx->jreq->buf.s);
747
+	ctx->jreq->root = srjson_Parse(ctx->jreq, ctx->jreq->buf.s);
748
+	if(ctx->jreq->root == NULL)
749
+	{
750
+		LM_ERR("invalid json doc [[%s]]\n", ctx->jreq->buf.s);
751
+		return NONSIP_MSG_PASS;
752
+	}
753
+	if (jsonrpc_init_reply(ctx) < 0) goto send_reply;
754
+
755
+	/* sanity checks on jsonrpc request */
756
+	nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "jsonrpc");
757
+	if(nj==NULL) {
758
+		LM_ERR("missing jsonrpc field in request\n");
759
+		goto send_reply;
760
+	}
761
+	val.s = nj->valuestring;
762
+	val.len = strlen(val.s);
763
+	if(val.len!=3 || strncmp(val.s, "2.0", 3)!=0) {
764
+		LM_ERR("unsupported jsonrpc version [%.*s]\n", val.len, val.s);
765
+		goto send_reply;
766
+	}
767
+	/* run jsonrpc command */
768
+	nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "method");
769
+	if(nj==NULL) {
770
+		LM_ERR("missing jsonrpc method field in request\n");
771
+		goto send_reply;
772
+	}
773
+	val.s = nj->valuestring;
774
+	val.len = strlen(val.s);
775
+	ctx->method = val.s;
776
+	rpce = find_rpc_export(ctx->method, 0);
777
+	if (!rpce || !rpce->function) {
778
+		LM_ERR("method callback not found [%.*s]\n", val.len, val.s);
779
+		jsonrpc_fault(ctx, 500, "Method Not Found");
780
+		goto send_reply;
781
+	}
782
+	ctx->flags = rpce->flags;
783
+	nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "params");
784
+	if(nj!=NULL && nj->type!=srjson_Array && nj->type!=srjson_Object) {
785
+		LM_ERR("params field is not an array or object\n");
786
+		goto send_reply;
787
+	}
788
+	if(nj!=NULL) ctx->req_node = nj->child;
789
+	rpce->function(&func_param, ctx);
790
+
791
+send_reply:
792
+	if (!ctx->reply_sent) {
793
+		ret = jsonrpc_send(ctx);
794
+	}
795
+	jsonrpc_clean_context(ctx);
796
+	if (ret < 0) return -1;
797
+	return 1;
798
+}
799
+
800
+
801
+/**
802
+ *
803
+ */
804
+static const char* jsonrpc_rpc_echo_doc[2] = {
805
+	"Sample echo command",
806
+	0
807
+};
808
+
809
+/**
810
+ *
811
+ */
812
+static void jsonrpc_rpc_echo(rpc_t* rpc, void* ctx)
813
+{
814
+	str sval;
815
+	int ival = 0;
816
+	int ret;
817
+	ret = rpc->scan(ctx, "S*d", &sval, &ival);
818
+	if(ret>0) {
819
+		LM_DBG("READ STR: %.*s\n", sval.len, sval.s);
820
+		rpc->add(ctx, "S", &sval);
821
+	}
822
+	if(ret>1) {
823
+		LM_DBG("READ INT: %d\n", ival);
824
+		rpc->add(ctx, "d", ival);
825
+	}
826
+}
827
+/**
828
+ *
829
+ */
830
+static rpc_export_t jsonrpc_rpc[] = {
831
+	{"jsonrpc.echo", jsonrpc_rpc_echo,  jsonrpc_rpc_echo_doc,       RET_ARRAY},
832
+	{0, 0, 0, 0}
833
+};
834
+
835
+/**
836
+ *
837
+ */
838
+static int jsonrpc_register_rpc(void)
839
+{
840
+	if (rpc_register_array(jsonrpc_rpc)!=0)
841
+	{
842
+		LM_ERR("failed to register RPC commands\n");
843
+		return -1;
844
+	}
845
+	return 0;
846
+}
0 847
new file mode 100644
... ...
@@ -0,0 +1,57 @@
1
+/**
2
+ *
3
+ * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.com) 
4
+ *
5
+ * This file is part of Kamailio, a free SIP server.
6
+ *
7
+ * Kamailio is free software; you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation; either version 2 of the License, or
10
+ * (at your option) any later version
11
+ *
12
+ * Kamailio is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program; if not, write to the Free Software
19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
+ *
21
+ */
22
+
23
+
24
+#ifndef _JSONRPC_S_H_
25
+#define _JSONRPC_S_H_
26
+
27
+#include "../../str.h"
28
+#include "../../rpc_lookup.h"
29
+#include "../../parser/msg_parser.h"
30
+#include "../../lib/srutils/srjson.h"
31
+
32
+
33
+/** The context of the jsonrpc request being processed.
34
+ *
35
+ * This is the data structure that contains all data related to the xhttp_rpc
36
+ * request being processed, such as the reply code and reason, data to be sent
37
+ * to the client in the reply, and so on.
38
+ *
39
+ * There is always one context per jsonrpc request.
40
+ */
41
+typedef struct jsonrpc_ctx {
42
+	sip_msg_t* msg;        /**< The SIP/HTTP received message. */
43
+	char* method;          /**< Name of the management function to be called */
44
+	unsigned int flags;    /**< Various flags, such as return value type */
45
+	srjson_doc_t *jreq;    /**< JSON request document */
46
+	srjson_t *req_node;    /**< Pointer to crt node in json req parameter list */
47
+	srjson_doc_t *jrpl;    /**< JSON reply document */
48
+	srjson_t *rpl_node;    /**< Pointer to crt node in json reply doc */
49
+	int reply_sent;        /**< Flag set if the json reply was sent */
50
+	int error_code;        /**< Json error code */
51
+	int http_code;         /**< http reply code */
52
+	str http_text;         /**< http reply reason text */
53
+} jsonrpc_ctx_t;
54
+
55
+
56
+#endif
57
+