1 /*
2  *
3   ***** BEGIN LICENSE BLOCK *****
4 
5   Copyright (C) 2009-2020 Olof Hagsand
6   Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
7 
8   This file is part of CLIXON.
9 
10   Licensed under the Apache License, Version 2.0 (the "License");
11   you may not use this file except in compliance with the License.
12   You may obtain a copy of the License at
13 
14     http://www.apache.org/licenses/LICENSE-2.0
15 
16   Unless required by applicable law or agreed to in writing, software
17   distributed under the License is distributed on an "AS IS" BASIS,
18   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   See the License for the specific language governing permissions and
20   limitations under the License.
21 
22   Alternatively, the contents of this file may be used under the terms of
23   the GNU General Public License Version 3 or later (the "GPL"),
24   in which case the provisions of the GPL are applicable instead
25   of those above. If you wish to allow use of your version of this file only
26   under the terms of the GPL, and not to allow others to
27   use your version of this file under the terms of Apache License version 2,
28   indicate your decision by deleting the provisions above and replace them with
29   the  notice and other provisions required by the GPL. If you do not delete
30   the provisions above, a recipient may use your version of this file under
31   the terms of any one of the Apache License version 2 or the GPL.
32 
33   ***** END LICENSE BLOCK *****
34   *
35   * Return errors
36   * @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
37  */
38 
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <signal.h>
44 #include <syslog.h>
45 #include <fcntl.h>
46 #include <ctype.h>
47 #include <time.h>
48 #include <signal.h>
49 #include <dlfcn.h>
50 #include <sys/param.h>
51 #include <sys/time.h>
52 #include <sys/wait.h>
53 
54 /* cligen */
55 #include <cligen/cligen.h>
56 
57 /* clicon */
58 #include <clixon/clixon.h>
59 
60 #include "restconf_lib.h"
61 #include "restconf_api.h"
62 #include "restconf_err.h"
63 
64 /*
65  * Constants
66  */
67 /* In the fcgi implementations some errors had body, it would be cleaner to skip them
68  * None seem mandatory according to RFC 7231
69  */
70 #define SKIP_BODY
71 
72 /*
73  * NOTE, fcgi seems not enough with a status code (libevhtp is) but must also have a status
74  * header.
75  */
76 
77 /*! HTTP error 400
78  * @param[in]  h    Clicon handle
79  * @param[in]  req  Generic Www handle
80  */
81 int
restconf_badrequest(clicon_handle h,void * req)82 restconf_badrequest(clicon_handle h,
83 		    void         *req)
84 {
85     int   retval = -1;
86 
87 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
88     if (restconf_reply_send(req, 400, NULL) < 0)
89 	goto done;
90     retval = 0;
91  done:
92 #else
93     char *path;
94     cbuf *cb = NULL;
95 
96     /* Create body */
97     if ((cb = cbuf_new()) == NULL){
98 	clicon_err(OE_UNIX, errno, "cbuf_new");
99 	goto done;
100     }
101     path = restconf_param_get("REQUEST_URI", r->envp);
102     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
103 	goto done;
104     cprintf(cb, "The requested URL %s or data is in some way badly formed.\n", path);
105     if (restconf_reply_send(req, 400, cb) < 0)
106 	goto done;
107     retval = 0;
108  done:
109     if (cb)
110 	cbuf_free(cb);
111 #endif
112     return retval;
113 }
114 
115 /*! HTTP error 401
116  * @param[in]  h    Clicon handle
117  * @param[in]  req  Generic Www handle
118  */
119 int
restconf_unauthorized(clicon_handle h,void * req)120 restconf_unauthorized(clicon_handle h,
121 		      void         *req)
122 
123 {
124     int   retval = -1;
125 
126 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
127     if (restconf_reply_send(req, 400, NULL) < 0)
128 	goto done;
129     retval = 0;
130  done:
131 #else
132     char *path;
133     cbuf *cb = NULL;
134 
135     /* Create body */
136     if ((cb = cbuf_new()) == NULL){
137 	clicon_err(OE_UNIX, errno, "cbuf_new");
138 	goto done;
139     }
140     path = restconf_param_get("REQUEST_URI", r->envp);
141     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
142 	goto done;
143     cprintf(cb, "<error-tag>access-denied</error-tag>\n");
144     cprintf(cb, "The requested URL %s was unauthorized.\n", path);
145     if (restconf_reply_send(req, 400, cb) < 0)
146 	goto done;
147     retval = 0;
148  done:
149     if (cb)
150 	cbuf_free(cb);
151 #endif
152     return retval;
153 }
154 
155 /*! HTTP error 403
156  * @param[in]  h    Clicon handle
157  * @param[in]  req  Generic Www handle
158  */
159 int
restconf_forbidden(clicon_handle h,void * req)160 restconf_forbidden(clicon_handle h,
161 		   void         *req)
162 {
163     int retval = -1;
164 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
165     if (restconf_reply_send(req, 403, NULL) < 0)
166 	goto done;
167     retval = 0;
168  done:
169 #else
170     char *path;
171     cbuf *cb = NULL;
172 
173     /* Create body */
174     if ((cb = cbuf_new()) == NULL){
175 	clicon_err(OE_UNIX, errno, "cbuf_new");
176 	goto done;
177     }
178     path = restconf_param_get("REQUEST_URI", r->envp);
179     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
180 	goto done;
181     cprintf(cb, "The requested URL %s was forbidden.\n", path);
182     if (restconf_reply_send(req, 403, cb) < 0)
183 	goto done;
184     retval = 0;
185  done:
186     if (cb)
187 	cbuf_free(cb);
188 #endif
189     return retval;
190 }
191 
192 /*! HTTP error 404
193  * @param[in]  h    Clicon handle
194  * @param[in]  req  Generic Www handle
195  * XXX skip body?
196  */
197 int
restconf_notfound(clicon_handle h,void * req)198 restconf_notfound(clicon_handle h,
199 		  void         *req)
200 {
201     int   retval = -1;
202 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
203     if (restconf_reply_send(req, 404, NULL) < 0)
204 	goto done;
205     retval = 0;
206  done:
207 #else
208     char *path;
209     cbuf *cb = NULL;
210 
211     /* Create body */
212     if ((cb = cbuf_new()) == NULL){
213 	clicon_err(OE_UNIX, errno, "cbuf_new");
214 	goto done;
215     }
216     path = restconf_param_get("REQUEST_URI", r->envp);
217     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
218 	goto done;
219     cprintf(cb, "The requested URL %s was not found on this server.\n", path);
220     if (restconf_reply_send(req, 404, cb) < 0)
221 	goto done;
222     retval = 0;
223  done:
224     if (cb)
225 	cbuf_free(cb);
226 #endif
227     return retval;
228 }
229 
230 /*! HTTP error 405
231  * @param[in]  req      Generic Www handle
232  * @param[in]  allow    Which methods are allowed
233  */
234 int
restconf_method_notallowed(void * req,char * allow)235 restconf_method_notallowed(void  *req,
236 			   char  *allow)
237 {
238     int retval = -1;
239 
240     if (restconf_reply_header(req, "Allow", "%s", allow) < 0)
241 	goto done;
242     if (restconf_reply_send(req, 405, NULL) < 0)
243 	goto done;
244     retval = 0;
245  done:
246     return retval;
247 }
248 
249 /*! HTTP error 406 Not acceptable
250  * @param[in]  h      Clicon handle
251  * @param[in]  req    Generic Www handle
252  */
253 int
restconf_notacceptable(clicon_handle h,void * req)254 restconf_notacceptable(clicon_handle h,
255 		       void         *req)
256 {
257     int retval = -1;
258 
259 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
260     if (restconf_reply_send(req, 406, NULL) < 0)
261 	goto done;
262     retval = 0;
263  done:
264 #else
265     char *path;
266     cbuf *cb = NULL;
267 
268     /* Create body */
269     if ((cb = cbuf_new()) == NULL){
270 	clicon_err(OE_UNIX, errno, "cbuf_new");
271 	goto done;
272     }
273     path = restconf_param_get("REQUEST_URI", r->envp);
274     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
275 	goto done;
276     cprintf(cb, "The target resource does not have a current representation that would be acceptable to the user agent.\n", path);
277     if (restconf_reply_send(req, 406, cb) < 0)
278 	goto done;
279     retval = 0;
280  done:
281     if (cb)
282 	cbuf_free(cb);
283 #endif
284     return retval;
285 }
286 
287 /*! HTTP error 409
288  * @param[in]  req      Generic Www handle
289  */
290 int
restconf_conflict(void * req)291 restconf_conflict(void    *req)
292 
293 {
294     int   retval = -1;
295 
296     if (restconf_reply_send(req, 409, NULL) < 0)
297 	goto done;
298     retval = 0;
299  done:
300     return retval;
301 }
302 
303 /*! HTTP error 409 Unsupporte dmedia
304  * @param[in]  req      Generic Www handle
305  */
306 int
restconf_unsupported_media(void * req)307 restconf_unsupported_media(void  *req)
308 {
309     int   retval = -1;
310 
311     if (restconf_reply_send(req, 415, NULL) < 0)
312 	goto done;
313     retval = 0;
314  done:
315     return retval;
316 }
317 
318 /*! HTTP error 500 Internal server error
319  * @param[in]  h      Clicon handle
320  * @param[in]  req    Generic Www handle
321  */
322 int
restconf_internal_server_error(clicon_handle h,void * req)323 restconf_internal_server_error(clicon_handle h,
324 			       void         *req)
325 {
326     int retval = -1;
327 #ifdef SKIP_BODY /* Remove the body - should it really be there? */
328     if (restconf_reply_send(req, 500, NULL) < 0)
329 	goto done;
330     retval = 0;
331  done:
332 #else
333     char *path;
334     cbuf *cb = NULL;
335 
336     /* Create body */
337     if ((cb = cbuf_new()) == NULL){
338 	clicon_err(OE_UNIX, errno, "cbuf_new");
339 	goto done;
340     }
341     path = restconf_param_get("REQUEST_URI", r->envp);
342     if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
343 	goto done;
344     cprintf(cb, "Internal server error when accessing %s</h1>\n", path);
345     if (restconf_reply_send(req, 500, cb) < 0)
346 	goto done;
347     retval = 0;
348  done:
349     if (cb)
350 	cbuf_free(cb);
351 #endif
352     return retval;
353 }
354 
355 /*! HTTP error 501 Not implemented
356  * @param[in]  req    Generic Www handle
357  */
358 int
restconf_notimplemented(void * req)359 restconf_notimplemented(void *req)
360 {
361     int   retval = -1;
362 
363     if (restconf_reply_send(req, 501, NULL) < 0)
364 	goto done;
365     retval = 0;
366  done:
367     return retval;
368 }
369 
370 /*! Generic restconf error function on get/head request
371  * @param[in]  h      Clixon handle
372  * @param[in]  req    Generic Www handle
373  * @param[in]  xerr   XML error message from backend
374  * @param[in]  pretty Set to 1 for pretty-printed xml/json output
375  * @param[in]  media  Output media
376  * @param[in]  code   If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
377  *                    otherwise use this code
378  */
379 int
api_return_err(clicon_handle h,void * req,cxobj * xerr,int pretty,restconf_media media,int code0)380 api_return_err(clicon_handle h,
381 	       void         *req,
382 	       cxobj        *xerr,
383 	       int           pretty,
384 	       restconf_media media,
385 	       int           code0)
386 {
387     int        retval = -1;
388     cbuf      *cb = NULL;
389     cbuf      *cberr = NULL;
390     cxobj     *xtag;
391     char      *tagstr;
392     int        code;
393     cxobj     *xerr2 = NULL;
394 
395     clicon_debug(1, "%s", __FUNCTION__);
396     if ((cb = cbuf_new()) == NULL){
397 	clicon_err(OE_UNIX, errno, "cbuf_new");
398 	goto done;
399     }
400     /* A well-formed error message when entering here should look like:
401      * <rpc-error>...<error-tag>invalid-value</error-tag>
402      * Check this is so, otherwise generate an internal error.
403      */
404     if (strcmp(xml_name(xerr), "rpc-error") != 0 ||
405 	(xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){
406 	if ((cberr = cbuf_new()) == NULL){
407 	    clicon_err(OE_UNIX, errno, "cbuf_new");
408 	    goto done;
409 	}
410 	cprintf(cberr, "Internal error, system returned invalid error message: ");
411 	if (netconf_err2cb(xerr, cberr) < 0)
412 	    goto done;
413 	if (netconf_operation_failed_xml(&xerr2, "application",
414 					 cbuf_get(cberr)) < 0)
415 	    goto done;
416 	if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){
417 	    clicon_err(OE_XML, 0, "Internal error, shouldnt happen");
418 	    goto done;
419 	}
420     }
421     if (xml_name_set(xerr, "error") < 0)
422 	goto done;
423     tagstr = xml_body(xtag);
424     if (code0 != 0)
425 	code = code0;
426     else{
427 	if ((code = restconf_err2code(tagstr)) < 0)
428 	    code = 500; /* internal server error */
429     }
430     if (restconf_reply_header(req, "Content_Type", "%s", restconf_media_int2str(media)) < 0)
431 	goto done;
432     switch (media){
433     case YANG_DATA_XML:
434 	clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
435 	if (pretty){
436 	    cprintf(cb, "    <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n");
437 	    if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
438 		goto done;
439 	    cprintf(cb, "    </errors>\r\n");
440 	}
441 	else {
442 	    cprintf(cb, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">");
443 	    if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
444 		goto done;
445 	    cprintf(cb, "</errors>\r\n");
446 	}
447 	break;
448     case YANG_DATA_JSON:
449 	clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
450 	if (pretty){
451 	    cprintf(cb, "{\n\"ietf-restconf:errors\" : ");
452 	    if (xml2json_cbuf(cb, xerr, pretty) < 0)
453 		goto done;
454 	    cprintf(cb, "\n}\r\n");
455 	}
456 	else{
457 	    cprintf(cb, "{");
458 	    cprintf(cb, "\"ietf-restconf:errors\":");
459 	    if (xml2json_cbuf(cb, xerr, pretty) < 0)
460 		goto done;
461 	    cprintf(cb, "}\r\n");
462 	}
463 	break;
464     default:
465 	clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
466 	goto done;
467 	break;
468     } /* switch media */
469     if (restconf_reply_send(req, code, cb) < 0)
470 	goto done;
471     // ok:
472     retval = 0;
473  done:
474     clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
475     if (cb)
476         cbuf_free(cb);
477     if (cberr)
478         cbuf_free(cberr);
479     return retval;
480 }
481