audit_logging: Remove duplciate error printing
[nivanova/samba-autobuild/.git] / lib / audit_logging / audit_logging.c
1 /*
2    common routines for audit logging
3
4    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
5
6    This program 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 3 of the License, or
9    (at your option) any later version.
10
11    This program 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, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21  * Error handling:
22  *
23  * The json_object structure contains a boolean 'error'.  This is set whenever
24  * an error is detected. All the library functions check this flag and return
25  * immediately if it is set.
26  *
27  *      if (object->error) {
28  *              return;
29  *      }
30  *
31  * This allows the operations to be sequenced naturally with out the clutter
32  * of error status checks.
33  *
34  *      audit = json_new_object();
35  *      json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR);
36  *      json_add_int(&audit, "statusCode", ret);
37  *      json_add_string(&audit, "status", ldb_strerror(ret));
38  *      json_add_string(&audit, "operation", operation);
39  *      json_add_address(&audit, "remoteAddress", remote);
40  *      json_add_sid(&audit, "userSid", sid);
41  *      json_add_string(&audit, "dn", dn);
42  *      json_add_guid(&audit, "transactionId", &ac->transaction_guid);
43  *      json_add_guid(&audit, "sessionId", unique_session_token);
44  *
45  * The assumptions are that errors will be rare, and that the audit logging
46  * code should not cause failures. So errors are logged but processing
47  * continues on a best effort basis.
48  */
49
50 #include "includes.h"
51
52 #include "librpc/ndr/libndr.h"
53 #include "lib/tsocket/tsocket.h"
54 #include "libcli/security/dom_sid.h"
55 #include "lib/messaging/messaging.h"
56 #include "auth/common_auth.h"
57 #include "audit_logging.h"
58
59 /*
60  * @brief Get a human readable timestamp.
61  *
62  * Returns the current time formatted as
63  *  "Tue, 14 Mar 2017 08:38:42.209028 NZDT"
64  *
65  * The returned string is allocated by talloc in the supplied context.
66  * It is the callers responsibility to free it.
67  *
68  * @param mem_ctx talloc memory context that owns the returned string.
69  *
70  * @return a human readable time stamp.
71  *
72  */
73 char* audit_get_timestamp(TALLOC_CTX *frame)
74 {
75         char buffer[40];        /* formatted time less usec and timezone */
76         char tz[10];            /* formatted time zone                   */
77         struct tm* tm_info;     /* current local time                    */
78         struct timeval tv;      /* current system time                   */
79         int r;                  /* response code from gettimeofday       */
80         char * ts;              /* formatted time stamp                  */
81
82         r = gettimeofday(&tv, NULL);
83         if (r) {
84                 DBG_ERR("Unable to get time of day: (%d) %s\n",
85                         errno,
86                         strerror(errno));
87                 return NULL;
88         }
89
90         tm_info = localtime(&tv.tv_sec);
91         if (tm_info == NULL) {
92                 DBG_ERR("Unable to determine local time\n");
93                 return NULL;
94         }
95
96         strftime(buffer, sizeof(buffer)-1, "%a, %d %b %Y %H:%M:%S", tm_info);
97         strftime(tz, sizeof(tz)-1, "%Z", tm_info);
98         ts = talloc_asprintf(frame, "%s.%06ld %s", buffer, tv.tv_usec, tz);
99         if (ts == NULL) {
100                 DBG_ERR("Out of memory formatting time stamp\n");
101         }
102         return ts;
103 }
104
105 /*
106  * @brief write an audit message to the audit logs.
107  *
108  * Write a human readable text audit message to the samba logs.
109  *
110  * @param prefix Text to be printed at the start of the log line
111  * @param message The content of the log line.
112  * @param debub_class The debug class to log the message with.
113  * @param debug_level The debug level to log the message with.
114  */
115 void audit_log_human_text(const char* prefix,
116                           const char* message,
117                           int debug_class,
118                           int debug_level)
119 {
120         DEBUGC(debug_class, debug_level, ("%s %s\n", prefix, message));
121 }
122
123 #ifdef HAVE_JANSSON
124 /*
125  * @brief write a json object to the samba audit logs.
126  *
127  * Write the json object to the audit logs as a formatted string
128  *
129  * @param prefix Text to be printed at the start of the log line
130  * @param message The content of the log line.
131  * @param debub_class The debug class to log the message with.
132  * @param debug_level The debug level to log the message with.
133  */
134 void audit_log_json(const char* prefix,
135                     struct json_object* message,
136                     int debug_class,
137                     int debug_level)
138 {
139         TALLOC_CTX *ctx = talloc_new(NULL);
140         char *s = json_to_string(ctx, message);
141         DEBUGC(debug_class, debug_level, ("JSON %s: %s\n", prefix, s));
142         TALLOC_FREE(ctx);
143 }
144
145 /*
146  * @brief get a connection to the messaging event server.
147  *
148  * Get a connection to the messaging event server registered by server_name.
149  *
150  * @param msg_ctx a valid imessaging_context.
151  * @param server_name name of messaging event server to connect to.
152  * @param server_id The event server details to populate
153  *
154  * @return NTSTATUS
155  */
156 static NTSTATUS get_event_server(
157         struct imessaging_context *msg_ctx,
158         const char *server_name,
159         struct server_id *event_server)
160 {
161         NTSTATUS status;
162         TALLOC_CTX *frame = talloc_stackframe();
163         unsigned num_servers, i;
164         struct server_id *servers;
165
166         status = irpc_servers_byname(
167                 msg_ctx,
168                 frame,
169                 server_name,
170                 &num_servers,
171                 &servers);
172
173         if (!NT_STATUS_IS_OK(status)) {
174                 DBG_NOTICE(
175                         "Failed to find '%s' registered on the message bus to "
176                         "send JSON audit events to: %s\n",
177                         server_name,
178                         nt_errstr(status));
179                 TALLOC_FREE(frame);
180                 return status;
181         }
182
183         /*
184          * Select the first server that is listening, because we get
185          * connection refused as NT_STATUS_OBJECT_NAME_NOT_FOUND
186          * without waiting
187          */
188         for (i = 0; i < num_servers; i++) {
189                 status = imessaging_send(
190                         msg_ctx,
191                         servers[i],
192                         MSG_PING,
193                         &data_blob_null);
194                 if (NT_STATUS_IS_OK(status)) {
195                         *event_server = servers[i];
196                         TALLOC_FREE(frame);
197                         return NT_STATUS_OK;
198                 }
199         }
200         DBG_NOTICE(
201                 "Failed to find '%s' registered on the message bus to "
202                 "send JSON audit events to: %s\n",
203                 server_name,
204                 nt_errstr(status));
205         TALLOC_FREE(frame);
206         return NT_STATUS_OBJECT_NAME_NOT_FOUND;
207 }
208
209 /*
210  * @brief send an audit message to a messaging event server.
211  *
212  * Send the message to a registered and listening event server.
213  * Note: Any errors are logged, and the message is not sent.  This is to ensure
214  *       that a poorly behaved event server does not impact Samba.
215  *
216  *       As it is possible to lose messages, especially during server
217  *       shut down, currently this function is primarily intended for use
218  *       in integration tests.
219  *
220  * @param msg_ctx an imessaging_context, can be NULL in which case no message
221  *                will be sent.
222  * @param server_name the naname of the event server to send the message to.
223  * @param messag_type A message type defined in librpc/idl/messaging.idl
224  * @param message The message to send.
225  *
226  */
227 void audit_message_send(
228         struct imessaging_context *msg_ctx,
229         const char *server_name,
230         uint32_t message_type,
231         struct json_object *message)
232 {
233         struct server_id event_server = {};
234         NTSTATUS status;
235
236         const char *message_string = NULL;
237         DATA_BLOB message_blob = data_blob_null;
238         TALLOC_CTX *ctx = talloc_new(NULL);
239
240         if (msg_ctx == NULL) {
241                 DBG_DEBUG("No messaging context\n");
242                 TALLOC_FREE(ctx);
243                 return;
244         }
245
246         /* Need to refetch the address each time as the destination server may
247          * have disconnected and reconnected in the interim, in which case
248          * messages may get lost
249          */
250         status = get_event_server(msg_ctx, server_name, &event_server);
251         if (!NT_STATUS_IS_OK(status)) {
252                 TALLOC_FREE(ctx);
253                 return;
254         }
255
256         message_string = json_to_string(ctx, message);
257         message_blob = data_blob_string_const(message_string);
258         status = imessaging_send(
259                 msg_ctx,
260                 event_server,
261                 message_type,
262                 &message_blob);
263
264         /*
265          * If the server crashed, try to find it again
266          */
267         if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
268                 status = get_event_server(msg_ctx, server_name, &event_server);
269                 if (!NT_STATUS_IS_OK(status)) {
270                         TALLOC_FREE(ctx);
271                         return;
272                 }
273                 imessaging_send(
274                         msg_ctx,
275                         event_server,
276                         message_type,
277                         &message_blob);
278         }
279         TALLOC_FREE(ctx);
280 }
281
282 /*
283  * @brief Create a new struct json_object, wrapping a JSON Object.
284  *
285  * Create a new json object, the json_object wraps the underlying json
286  * implementations JSON Object representation.
287  *
288  * Free with a call to json_free_object, note that the jansson inplementation
289  * allocates memory with malloc and not talloc.
290  *
291  * @return a struct json_object, error will be set to true if the object
292  *         could not be created.
293  *
294  */
295 struct json_object json_new_object(void) {
296
297         struct json_object object;
298         object.error = false;
299
300         object.root = json_object();
301         if (object.root == NULL) {
302                 object.error = true;
303                 DBG_ERR("Unable to create json_object\n");
304         }
305         return object;
306 }
307
308 /*
309  * @brief Create a new struct json_object wrapping a JSON Array.
310  *
311  * Create a new json object, the json_object wraps the underlying json
312  * implementations JSON Array representation.
313  *
314  * Free with a call to json_free_object, note that the jansson inplementation
315  * allocates memory with malloc and not talloc.
316  *
317  * @return a struct json_object, error will be set to true if the array
318  *         could not be created.
319  *
320  */
321 struct json_object json_new_array(void) {
322
323         struct json_object array;
324         array.error = false;
325
326         array.root = json_array();
327         if (array.root == NULL) {
328                 array.error = true;
329                 DBG_ERR("Unable to create json_array\n");
330         }
331         return array;
332 }
333
334
335 /*
336  * @brief free and invalidate a previously created JSON object.
337  *
338  * Release any resources owned by a json_object, and then mark the structure
339  * as invalid.  It is safe to call this multiple times on an object.
340  *
341  */
342 void json_free(struct json_object *object)
343 {
344         if (object->root != NULL) {
345                 json_decref(object->root);
346         }
347         object->root = NULL;
348         object->error = true;
349 }
350
351 /*
352  * @brief is the current JSON object invalid?
353  *
354  * Check the state of the object to determine if it is invalid.
355  *
356  * @return is the object valid?
357  *
358  */
359 bool json_is_invalid(struct json_object *object)
360 {
361         return object->error;
362 }
363
364 /*
365  * @brief Add an integer value to a JSON object.
366  *
367  * Add an integer value named 'name' to the json object.
368  * In the event of an error object will be invalidated.
369  *
370  * @param object the JSON object to be updated.
371  * @param name the name of the value.
372  * @param value the value.
373  *
374  */
375 void json_add_int(struct json_object *object,
376                   const char* name,
377                   const int value)
378 {
379         int rc = 0;
380
381         if (object->error) {
382                 return;
383         }
384
385         rc = json_object_set_new(object->root, name, json_integer(value));
386         if (rc) {
387                 DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
388                 object->error = true;
389         }
390 }
391
392 /*
393  * @brief Add a boolean value to a JSON object.
394  *
395  * Add a boolean value named 'name' to the json object.
396  * In the event of an error object will be invalidated.
397  *
398  * @param object the JSON object to be updated.
399  * @param name the name.
400  * @param value the value.
401  *
402  */
403 void json_add_bool(struct json_object *object,
404                    const char* name,
405                    const bool value)
406 {
407         int rc = 0;
408
409         if (object->error) {
410                 return;
411         }
412
413         rc = json_object_set_new(object->root, name, json_boolean(value));
414         if (rc) {
415                 DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
416                 object->error = true;
417         }
418
419 }
420
421 /*
422  * @brief Add a string value to a JSON object.
423  *
424  * Add a string value named 'name' to the json object.
425  * In the event of an error object will be invalidated.
426  *
427  * @param object the JSON object to be updated.
428  * @param name the name.
429  * @param value the value.
430  *
431  */
432 void json_add_string(struct json_object *object,
433                      const char* name,
434                      const char* value)
435 {
436         int rc = 0;
437
438         if (object->error) {
439                 return;
440         }
441
442         if (value) {
443                 rc = json_object_set_new(
444                         object->root,
445                         name,
446                         json_string(value));
447         } else {
448                 rc = json_object_set_new(object->root, name, json_null());
449         }
450         if (rc) {
451                 DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
452                 object->error = true;
453         }
454 }
455
456 /*
457  * @brief Assert that the current JSON object is an array.
458  *
459  * Check that the current object is a JSON array, and if not
460  * invalidate the object. We also log an error message as this indicates
461  * bug in the calling code.
462  *
463  * @param object the JSON object to be validated.
464  */
465 void json_assert_is_array(struct json_object *array) {
466
467         if (array->error) {
468                 return;
469         }
470
471         if (json_is_array(array->root) == false) {
472                 DBG_ERR("JSON object is not an array\n");
473                 array->error = true;
474                 return;
475         }
476 }
477
478 /*
479  * @brief Add a JSON object to a JSON object.
480  *
481  * Add a JSON object named 'name' to the json object.
482  * In the event of an error object will be invalidated.
483  *
484  * @param object the JSON object to be updated.
485  * @param name the name.
486  * @param value the value.
487  *
488  */
489 void json_add_object(struct json_object *object,
490                      const char* name,
491                      struct json_object *value)
492 {
493         int rc = 0;
494         json_t *jv = NULL;
495
496         if (object->error) {
497                 return;
498         }
499
500         if (value != NULL && value->error) {
501                 object->error = true;
502                 return;
503         }
504
505         jv = value == NULL ? json_null() : value->root;
506
507         if (json_is_array(object->root)) {
508                 rc = json_array_append_new(object->root, jv);
509         } else if (json_is_object(object->root)) {
510                 rc = json_object_set_new(object->root, name,  jv);
511         } else {
512                 DBG_ERR("Invalid JSON object type\n");
513                 object->error = true;
514         }
515         if (rc) {
516                 DBG_ERR("Unable to add object [%s]\n", name);
517                 object->error = true;
518         }
519 }
520
521 /*
522  * @brief Add a string to a JSON object, truncating if necessary.
523  *
524  *
525  * Add a string value named 'name' to the json object, the string will be
526  * truncated if it is more than len characters long. If len is 0 the value
527  * is encoded as a JSON null.
528  *
529  * In the event of an error object will be invalidated.
530  *
531  * @param object the JSON object to be updated.
532  * @param name the name.
533  * @param value the value.
534  * @param len the maximum number of characters to be copied.
535  *
536  */
537 void json_add_stringn(struct json_object *object,
538                       const char *name,
539                       const char *value,
540                       const size_t len)
541 {
542
543         int rc = 0;
544         if (object->error) {
545                 return;
546         }
547
548         if (value != NULL && len > 0) {
549                 char buffer[len+1];
550                 strncpy(buffer, value, len);
551                 buffer[len] = '\0';
552                 rc = json_object_set_new(object->root,
553                                          name,
554                                          json_string(buffer));
555         } else {
556                 rc = json_object_set_new(object->root, name, json_null());
557         }
558         if (rc) {
559                 DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
560                 object->error = true;
561         }
562 }
563
564 /*
565  * @brief Add a version object to a JSON object
566  *
567  * Add a version object to the JSON object
568  *      "version":{"major":1, "minor":0}
569  *
570  * The version tag is intended to aid the processing of the JSON messages
571  * The major version number should change when an attribute is:
572  *  - renamed
573  *  - removed
574  *  - its meaning changes
575  *  - its contents change format
576  * The minor version should change whenever a new attribute is added and for
577  * minor bug fixes to an attributes content.
578  *
579  * In the event of an error object will be invalidated.
580  *
581  * @param object the JSON object to be updated.
582  * @param major the major version number
583  * @param minor the minor version number
584  */
585 void json_add_version(struct json_object *object, int major, int minor)
586 {
587         struct json_object version = json_new_object();
588         json_add_int(&version, "major", major);
589         json_add_int(&version, "minor", minor);
590         json_add_object(object, "version", &version);
591 }
592
593 /*
594  * @brief add an ISO 8601 timestamp to the object.
595  *
596  * Add the current date and time as a timestamp in ISO 8601 format
597  * to a JSON object
598  *
599  * "timestamp":"2017-03-06T17:18:04.455081+1300"
600  *
601  * In the event of an error object will be invalidated.
602  *
603  * @param object the JSON object to be updated.
604  */
605 void json_add_timestamp(struct json_object *object)
606 {
607         char buffer[40];        /* formatted time less usec and timezone */
608         char timestamp[65];     /* the formatted ISO 8601 time stamp     */
609         char tz[10];            /* formatted time zone                   */
610         struct tm* tm_info;     /* current local time                    */
611         struct timeval tv;      /* current system time                   */
612         int r;                  /* response code from gettimeofday       */
613
614         if (object->error) {
615                 return;
616         }
617
618         r = gettimeofday(&tv, NULL);
619         if (r) {
620                 DBG_ERR("Unable to get time of day: (%d) %s\n",
621                         errno,
622                         strerror(errno));
623                 object->error = true;
624                 return;
625         }
626
627         tm_info = localtime(&tv.tv_sec);
628         if (tm_info == NULL) {
629                 DBG_ERR("Unable to determine local time\n");
630                 object->error = true;
631                 return;
632         }
633
634         strftime(buffer, sizeof(buffer)-1, "%Y-%m-%dT%T", tm_info);
635         strftime(tz, sizeof(tz)-1, "%z", tm_info);
636         snprintf(
637                 timestamp,
638                 sizeof(timestamp),
639                 "%s.%06ld%s",
640                 buffer,
641                 tv.tv_usec,
642                 tz);
643         json_add_string(object, "timestamp", timestamp);
644 }
645
646
647 /*
648  *@brief Add a tsocket_address to a JSON object
649  *
650  * Add the string representation of a Samba tsocket_address to the object.
651  *
652  * "localAddress":"ipv6::::0"
653  *
654  * In the event of an error object will be invalidated.
655  *
656  * @param object the JSON object to be updated.
657  * @param name the name.
658  * @param address the tsocket_address.
659  *
660  */
661 void json_add_address(struct json_object *object,
662                       const char *name,
663                       const struct tsocket_address *address)
664 {
665
666         if (object->error) {
667                 return;
668         }
669         if (address == NULL) {
670                 int rc = json_object_set_new(object->root, name, json_null());
671                 if (rc) {
672                         DBG_ERR("Unable to set address [%s] to null\n", name);
673                         object->error = true;
674                 }
675         } else {
676                 TALLOC_CTX *ctx = talloc_new(NULL);
677                 char *s = NULL;
678
679                 s = tsocket_address_string(address, ctx);
680                 json_add_string(object, name, s);
681                 TALLOC_FREE(ctx);
682         }
683 }
684
685 /*
686  * @brief Add a formatted string representation of a sid to a json object.
687  *
688  * Add the string representation of a Samba sid to the object.
689  *
690  * "sid":"S-1-5-18"
691  *
692  * In the event of an error object will be invalidated.
693  *
694  * @param object the JSON object to be updated.
695  * @param name the name.
696  * @param sid the sid
697  *
698  */
699 void json_add_sid(struct json_object *object,
700                   const char *name,
701                   const struct dom_sid *sid)
702 {
703
704         if (object->error) {
705                 return;
706         }
707         if (sid == NULL) {
708                 int rc = json_object_set_new(object->root, name, json_null());
709                 if (rc) {
710                         DBG_ERR("Unable to set SID [%s] to null\n", name);
711                         object->error = true;
712                 }
713         } else {
714                 char sid_buf[DOM_SID_STR_BUFLEN];
715
716                 dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
717                 json_add_string(object, name, sid_buf);
718         }
719 }
720
721 /*
722  * @brief Add a formatted string representation of a guid to a json object.
723  *
724  * Add the string representation of a Samba GUID to the object.
725  *
726  * "guid":"1fb9f2ee-2a4d-4bf8-af8b-cb9d4529a9ab"
727  *
728  * In the event of an error object will be invalidated.
729  *
730  * @param object the JSON object to be updated.
731  * @param name the name.
732  * @param guid the guid.
733  *
734  *
735  */
736 void json_add_guid(struct json_object *object,
737                    const char *name,
738                    const struct GUID *guid)
739 {
740
741
742         if (object->error) {
743                 return;
744         }
745         if (guid == NULL) {
746                 int rc = json_object_set_new(object->root, name, json_null());
747                 if (rc) {
748                         DBG_ERR("Unable to set GUID [%s] to null\n", name);
749                         object->error = true;
750                 }
751         } else {
752                 char *guid_str;
753                 struct GUID_txt_buf guid_buff;
754
755                 guid_str = GUID_buf_string(guid, &guid_buff);
756                 json_add_string(object, name, guid_str);
757         }
758 }
759
760
761 /*
762  * @brief Convert a JSON object into a string
763  *
764  * Convert the jsom object into a string suitable for printing on a log line,
765  * i.e. with no embedded line breaks.
766  *
767  * If the object is invalid it returns NULL.
768  *
769  * @param mem_ctx the talloc memory context owning the returned string
770  * @param object the json object.
771  *
772  * @return A string representation of the object or NULL if the object
773  *         is invalid.
774  */
775 char *json_to_string(TALLOC_CTX *mem_ctx,
776                      struct json_object *object)
777 {
778         char *json = NULL;
779         char *json_string = NULL;
780
781         if (object->error) {
782                 return NULL;
783         }
784
785         /*
786          * json_dumps uses malloc, so need to call free(json) to release
787          * the memory
788          */
789         json = json_dumps(object->root, 0);
790         if (json == NULL) {
791                 DBG_ERR("Unable to convert JSON object to string\n");
792                 return NULL;
793         }
794
795         json_string = talloc_strdup(mem_ctx, json);
796         if (json_string == NULL) {
797                 free(json);
798                 DBG_ERR("Unable to copy JSON object string to talloc string\n");
799                 return NULL;
800         }
801         free(json);
802
803         return json_string;
804 }
805
806 /*
807  * @brief get a json array named "name" from the json object.
808  *
809  * Get the array attribute named name, creating it if it does not exist.
810  *
811  * @param object the json object.
812  * @param name the name of the array attribute
813  *
814  * @return The array object, will be created if it did not exist.
815  */
816 struct json_object json_get_array(struct json_object *object,
817                                   const char* name)
818 {
819
820         struct json_object array = json_new_array();
821         json_t *a = NULL;
822
823         if (object->error) {
824                 array.error = true;
825                 return array;
826         }
827
828         a = json_object_get(object->root, name);
829         if (a == NULL) {
830                 return array;
831         }
832         json_array_extend(array.root, a);
833
834         return array;
835 }
836
837 /*
838  * @brief get a json object named "name" from the json object.
839  *
840  * Get the object attribute named name, creating it if it does not exist.
841  *
842  * @param object the json object.
843  * @param name the name of the object attribute
844  *
845  * @return The object, will be created if it did not exist.
846  */
847 struct json_object json_get_object(struct json_object *object,
848                                    const char* name)
849 {
850
851         struct json_object o = json_new_object();
852         json_t *v = NULL;
853
854         if (object->error) {
855                 o.error = true;
856                 return o;
857         }
858
859         v = json_object_get(object->root, name);
860         if (v == NULL) {
861                 return o;
862         }
863         json_object_update(o.root, v);
864
865         return o;
866 }
867 #endif