ubus: split out session handling code into ubus-session.c
[project/uhttpd.git] / ubus.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
6  *
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  */
19
20
21 #include <libubox/blobmsg.h>
22 #include <libubox/blobmsg_json.h>
23 #include <libubox/avl.h>
24 #include <libubox/avl-cmp.h>
25 #include <stdio.h>
26 #include <poll.h>
27
28 #include "uhttpd.h"
29 #include "plugin.h"
30 #include "ubus-session.h"
31
32 static const struct uhttpd_ops *ops;
33 static struct config *_conf;
34 #define conf (*_conf)
35
36 static struct ubus_context *ctx;
37 static struct blob_buf buf;
38
39 #define UH_UBUS_MAX_POST_SIZE   4096
40
41 static char *split_str(char *str)
42 {
43         if (str)
44                 str = strchr(str, '/');
45
46         while (str && *str == '/') {
47                 *str = 0;
48                 str++;
49         }
50         return str;
51 }
52
53 static bool
54 uh_ubus_request_parse_url(struct client *cl, char *url, char **sid, char **obj, char **fun)
55 {
56         url += strlen(conf.ubus_prefix);
57         while (url && *url == '/')
58                 url++;
59
60         *sid = url;
61
62         url = split_str(url);
63         *obj = url;
64
65         url = split_str(url);
66         *fun = url;
67
68         return *sid && *obj && *fun;
69 }
70
71 static void
72 uh_ubus_request_data_cb(struct ubus_request *req, int type, struct blob_attr *msg)
73 {
74         struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
75         struct client *cl = container_of(du, struct client, dispatch.ubus);
76         char *str;
77
78         if (!du->header_sent) {
79                 ops->http_header(cl, 200, "OK");
80                 ustream_printf(cl->us, "Content-Type: application/json\r\n\r\n");
81                 du->header_sent = true;
82         }
83
84         str = blobmsg_format_json_indent(msg, true, 0);
85         ops->chunk_write(cl, str, strlen(str));
86         free(str);
87 }
88
89 static void
90 uh_ubus_request_cb(struct ubus_request *req, int ret)
91 {
92         struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
93         struct client *cl = container_of(du, struct client, dispatch.ubus);
94
95         if (!du->header_sent)
96                 return ops->client_error(cl, 204, "No content", "Function did not return data");
97
98         ops->request_done(cl);
99 }
100
101 static void uh_ubus_close_fds(struct client *cl)
102 {
103         if (ctx->sock.fd < 0)
104                 return;
105
106         close(ctx->sock.fd);
107         ctx->sock.fd = -1;
108 }
109
110 static void uh_ubus_request_free(struct client *cl)
111 {
112         struct dispatch_ubus *du = &cl->dispatch.ubus;
113
114         if (du->jsobj)
115                 json_object_put(du->jsobj);
116
117         if (du->jstok)
118                 json_tokener_free(du->jstok);
119
120         if (du->req_pending)
121                 ubus_abort_request(ctx, &du->req);
122 }
123
124 static void uh_ubus_json_error(struct client *cl)
125 {
126         ops->client_error(cl, 400, "Bad Request", "Invalid JSON data");
127 }
128
129 static void uh_ubus_send_request(struct client *cl, json_object *obj)
130 {
131         struct dispatch *d = &cl->dispatch;
132         struct dispatch_ubus *du = &d->ubus;
133         int ret;
134
135         blob_buf_init(&buf, 0);
136
137         if (obj && !blobmsg_add_object(&buf, obj))
138                 return uh_ubus_json_error(cl);
139
140         ret = ubus_invoke_async(ctx, du->obj, du->func, buf.head, &du->req);
141         if (ret)
142                 return ops->client_error(cl, 500, "Internal Error",
143                         "Error sending ubus request: %s", ubus_strerror(ret));
144
145         du->req.data_cb = uh_ubus_request_data_cb;
146         du->req.complete_cb = uh_ubus_request_cb;
147         ubus_complete_request_async(ctx, &du->req);
148
149         du->req_pending = true;
150 }
151
152 static void uh_ubus_data_done(struct client *cl)
153 {
154         struct dispatch_ubus *du = &cl->dispatch.ubus;
155         struct json_object *obj = du->jsobj;
156
157         if (!obj || json_object_get_type(obj) != json_type_object)
158                 return uh_ubus_json_error(cl);
159
160         uh_ubus_send_request(cl, obj);
161 }
162
163 static int uh_ubus_data_send(struct client *cl, const char *data, int len)
164 {
165         struct dispatch_ubus *du = &cl->dispatch.ubus;
166
167         if (du->jsobj) {
168                 uh_ubus_json_error(cl);
169                 return 0;
170         }
171
172         du->post_len += len;
173         if (du->post_len > UH_UBUS_MAX_POST_SIZE) {
174                 ops->client_error(cl, 413, "Too Large", "Message too big");
175                 return 0;
176         }
177
178         du->jsobj = json_tokener_parse_ex(du->jstok, data, len);
179         return len;
180 }
181
182 static void uh_ubus_defer_post(struct client *cl)
183 {
184         struct dispatch *d = &cl->dispatch;
185
186         d->ubus.jstok = json_tokener_new();
187         if (d->ubus.jstok)
188                 return ops->client_error(cl, 500, "Internal Error", "Internal Error");
189
190         d->data_send = uh_ubus_data_send;
191         d->data_done = uh_ubus_data_done;
192 }
193
194 static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi)
195 {
196         struct uh_ubus_session *ses;
197         struct dispatch *d = &cl->dispatch;
198         char *sid, *obj, *fun;
199
200         blob_buf_init(&buf, 0);
201
202         if (!uh_ubus_request_parse_url(cl, url, &sid, &obj, &fun))
203                 return ops->client_error(cl, 400, "Bad Request", "Invalid Request");
204
205         ses = uh_ubus_session_get(sid);
206         if (!ses)
207                 return ops->client_error(cl, 404, "Not Found", "No such session %s", sid);
208
209         if (!uh_ubus_session_acl_allowed(ses, obj, fun))
210                 return ops->client_error(cl, 403, "Denied", "Access to object denied");
211
212         if (ubus_lookup_id(ctx, obj, &d->ubus.obj))
213                 return ops->client_error(cl, 500, "Not Found", "No such object");
214
215         d->close_fds = uh_ubus_close_fds;
216         d->free = uh_ubus_request_free;
217         d->ubus.func = fun;
218
219         if (cl->request.method == UH_HTTP_MSG_POST)
220                 uh_ubus_defer_post(cl);
221         else
222                 uh_ubus_send_request(cl, NULL);
223 }
224
225 static bool
226 uh_ubus_check_url(const char *url)
227 {
228         return ops->path_match(conf.ubus_prefix, url);
229 }
230
231 static int
232 uh_ubus_init(void)
233 {
234         static struct dispatch_handler ubus_dispatch = {
235                 .check_url = uh_ubus_check_url,
236                 .handle_request = uh_ubus_handle_request,
237         };
238
239         ctx = ubus_connect(conf.ubus_socket);
240         if (!ctx) {
241                 fprintf(stderr, "Unable to connect to ubus socket\n");
242                 exit(1);
243         }
244
245         ops->dispatch_add(&ubus_dispatch);
246         if (ubus_session_api_init(ctx)) {
247                 fprintf(stderr, "Unable to initialize ubus session API\n");
248                 exit(1);
249         }
250
251         uloop_done();
252         return 0;
253 }
254
255
256 static int uh_ubus_plugin_init(const struct uhttpd_ops *o, struct config *c)
257 {
258         ops = o;
259         _conf = c;
260         return uh_ubus_init();
261 }
262
263 static void uh_ubus_post_init(void)
264 {
265         ubus_add_uloop(ctx);
266 }
267
268 const struct uhttpd_plugin uhttpd_plugin = {
269         .init = uh_ubus_plugin_init,
270         .post_init = uh_ubus_post_init,
271 };