Merge pull request #1686 from rubenk/zfs-arc
[collectd.git] / src / openldap.c
1 /**
2  * collectd - src/openldap.c
3  * Copyright (C) 2011       Kimo Rosenbaum
4  * Copyright (C) 2014       Marc Fournier
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *
24  * Authors:
25  *   Kimo Rosenbaum <kimor79 at yahoo.com>
26  *   Marc Fournier <marc.fournier at camptocamp.com>
27  **/
28
29 #include "collectd.h"
30 #include "common.h"
31 #include "plugin.h"
32 #include "configfile.h"
33
34 #if defined(__APPLE__)
35 #pragma clang diagnostic push
36 #pragma clang diagnostic warning "-Wdeprecated-declarations"
37 #endif
38
39 #include <lber.h>
40 #include <ldap.h>
41
42 struct cldap_s /* {{{ */
43 {
44         char *name;
45
46         char *binddn;
47         char *password;
48         char *cacert;
49         char *host;
50         int   state;
51         _Bool starttls;
52         int   timeout;
53         char *url;
54         _Bool verifyhost;
55         int   version;
56
57         LDAP *ld;
58 };
59 typedef struct cldap_s cldap_t; /* }}} */
60
61 static void cldap_free (cldap_t *st) /* {{{ */
62 {
63         if (st == NULL)
64                 return;
65
66         sfree (st->binddn);
67         sfree (st->password);
68         sfree (st->cacert);
69         sfree (st->host);
70         sfree (st->name);
71         sfree (st->url);
72         if (st->ld)
73                 ldap_memfree (st->ld);
74         sfree (st);
75 } /* }}} void cldap_free */
76
77 /* initialize ldap for each host */
78 static int cldap_init_host (cldap_t *st) /* {{{ */
79 {
80         LDAP *ld;
81         int rc;
82         rc = ldap_initialize (&ld, st->url);
83         if (rc != LDAP_SUCCESS)
84         {
85                 ERROR ("openldap plugin: ldap_initialize failed: %s",
86                         ldap_err2string (rc));
87                 st->state = 0;
88                 ldap_unbind_ext_s (ld, NULL, NULL);
89                 return (-1);
90         }
91
92         st->ld = ld;
93
94         ldap_set_option (st->ld, LDAP_OPT_PROTOCOL_VERSION, &st->version);
95
96         ldap_set_option (st->ld, LDAP_OPT_TIMEOUT,
97                 &(const struct timeval){st->timeout, 0});
98
99         if (st->cacert != NULL)
100                 ldap_set_option (st->ld, LDAP_OPT_X_TLS_CACERTFILE, st->cacert);
101
102         if (st->verifyhost == 0)
103         {
104                 int never = LDAP_OPT_X_TLS_NEVER;
105                 ldap_set_option (st->ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &never);
106         }
107
108         if (st->starttls != 0)
109         {
110                 rc = ldap_start_tls_s (ld, NULL, NULL);
111                 if (rc != LDAP_SUCCESS)
112                 {
113                         ERROR ("openldap plugin: Failed to start tls on %s: %s",
114                                         st->url, ldap_err2string (rc));
115                         st->state = 0;
116                         ldap_unbind_ext_s (st->ld, NULL, NULL);
117                         return (-1);
118                 }
119         }
120
121         struct berval cred;
122         if (st->password != NULL)
123         {
124                 cred.bv_val = st->password;
125                 cred.bv_len = strlen (st->password);
126         }
127         else
128         {
129                 cred.bv_val = "";
130                 cred.bv_len = 0;
131         }
132
133         rc = ldap_sasl_bind_s (st->ld, st->binddn, LDAP_SASL_SIMPLE, &cred,
134                         NULL, NULL, NULL);
135         if (rc != LDAP_SUCCESS)
136         {
137                 ERROR ("openldap plugin: Failed to bind to %s: %s",
138                                 st->url, ldap_err2string (rc));
139                 st->state = 0;
140                 ldap_unbind_ext_s (st->ld, NULL, NULL);
141                 return (-1);
142         }
143         else
144         {
145                 DEBUG ("openldap plugin: Successfully connected to %s",
146                                 st->url);
147                 st->state = 1;
148                 return (0);
149         }
150 } /* }}} static cldap_init_host */
151
152 static void cldap_submit_value (const char *type, const char *type_instance, /* {{{ */
153                 value_t value, cldap_t *st)
154 {
155         value_list_t vl = VALUE_LIST_INIT;
156
157         vl.values     = &value;
158         vl.values_len = 1;
159
160         if ((st->host == NULL)
161                         || (strcmp ("", st->host) == 0)
162                         || (strcmp ("localhost", st->host) == 0))
163         {
164                 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
165         }
166         else
167         {
168                 sstrncpy (vl.host, st->host, sizeof (vl.host));
169         }
170
171         sstrncpy (vl.plugin, "openldap", sizeof (vl.plugin));
172         if (st->name != NULL)
173                 sstrncpy (vl.plugin_instance, st->name,
174                                 sizeof (vl.plugin_instance));
175
176         sstrncpy (vl.type, type, sizeof (vl.type));
177         if (type_instance != NULL)
178                 sstrncpy (vl.type_instance, type_instance,
179                                 sizeof (vl.type_instance));
180
181         plugin_dispatch_values (&vl);
182 } /* }}} void cldap_submit_value */
183
184 static void cldap_submit_derive (const char *type, const char *type_instance, /* {{{ */
185                 derive_t d, cldap_t *st)
186 {
187         value_t v;
188         v.derive = d;
189         cldap_submit_value (type, type_instance, v, st);
190 } /* }}} void cldap_submit_derive */
191
192 static void cldap_submit_gauge (const char *type, const char *type_instance, /* {{{ */
193                 gauge_t g, cldap_t *st)
194 {
195         value_t v;
196         v.gauge = g;
197         cldap_submit_value (type, type_instance, v, st);
198 } /* }}} void cldap_submit_gauge */
199
200 static int cldap_read_host (user_data_t *ud) /* {{{ */
201 {
202         cldap_t *st;
203         LDAPMessage *e, *result;
204         char *dn;
205         int rc;
206         int status;
207
208         char *attrs[9] = { "monitorCounter",
209                                 "monitorOpCompleted",
210                                 "monitorOpInitiated",
211                                 "monitoredInfo",
212                                 "olmBDBEntryCache",
213                                 "olmBDBDNCache",
214                                 "olmBDBIDLCache",
215                                 "namingContexts",
216                                 NULL };
217
218         if ((ud == NULL) || (ud->data == NULL))
219         {
220                 ERROR ("openldap plugin: cldap_read_host: Invalid user data.");
221                 return (-1);
222         }
223
224         st = (cldap_t *) ud->data;
225
226         status = cldap_init_host (st);
227         if (status != 0)
228                 return (-1);
229
230         rc = ldap_search_ext_s (st->ld, "cn=Monitor", LDAP_SCOPE_SUBTREE,
231                 "(|(!(cn=* *))(cn=Database*))", attrs, 0,
232                 NULL, NULL, NULL, 0, &result);
233
234         if (rc != LDAP_SUCCESS)
235         {
236                 ERROR ("openldap plugin: Failed to execute search: %s",
237                                 ldap_err2string (rc));
238                 ldap_msgfree (result);
239                 ldap_unbind_ext_s (st->ld, NULL, NULL);
240                 return (-1);
241         }
242
243         for (e = ldap_first_entry (st->ld, result); e != NULL;
244                 e = ldap_next_entry (st->ld, e))
245         {
246                 if ((dn = ldap_get_dn (st->ld, e)) != NULL)
247                 {
248                         unsigned long long counter = 0;
249                         unsigned long long opc = 0;
250                         unsigned long long opi = 0;
251                         unsigned long long info = 0;
252
253                         struct berval counter_data;
254                         struct berval opc_data;
255                         struct berval opi_data;
256                         struct berval info_data;
257                         struct berval olmbdb_data;
258                         struct berval nc_data;
259
260                         struct berval **counter_list;
261                         struct berval **opc_list;
262                         struct berval **opi_list;
263                         struct berval **info_list;
264                         struct berval **olmbdb_list;
265                         struct berval **nc_list;
266
267                         if ((counter_list = ldap_get_values_len (st->ld, e,
268                                 "monitorCounter")) != NULL)
269                         {
270                                 counter_data = *counter_list[0];
271                                 counter = atoll (counter_data.bv_val);
272                         }
273
274                         if ((opc_list = ldap_get_values_len (st->ld, e,
275                                 "monitorOpCompleted")) != NULL)
276                         {
277                                 opc_data = *opc_list[0];
278                                 opc = atoll (opc_data.bv_val);
279                         }
280
281                         if ((opi_list = ldap_get_values_len (st->ld, e,
282                                 "monitorOpInitiated")) != NULL)
283                         {
284                                 opi_data = *opi_list[0];
285                                 opi = atoll (opi_data.bv_val);
286                         }
287
288                         if ((info_list = ldap_get_values_len (st->ld, e,
289                                 "monitoredInfo")) != NULL)
290                         {
291                                 info_data = *info_list[0];
292                                 info = atoll (info_data.bv_val);
293                         }
294
295                         if (strcmp (dn, "cn=Total,cn=Connections,cn=Monitor")
296                                         == 0)
297                         {
298                                 cldap_submit_derive ("total_connections", NULL,
299                                         counter, st);
300                         }
301                         else if (strcmp (dn,
302                                         "cn=Current,cn=Connections,cn=Monitor")
303                                         == 0)
304                         {
305                                 cldap_submit_gauge ("current_connections", NULL,
306                                         counter, st);
307                         }
308                         else if (strcmp (dn,
309                                         "cn=Operations,cn=Monitor") == 0)
310                         {
311                                 cldap_submit_derive ("operations",
312                                         "completed", opc, st);
313                                 cldap_submit_derive ("operations",
314                                         "initiated", opi, st);
315                         }
316                         else if (strcmp (dn,
317                                         "cn=Bind,cn=Operations,cn=Monitor")
318                                         == 0)
319                         {
320                                 cldap_submit_derive ("operations",
321                                         "bind-completed", opc, st);
322                                 cldap_submit_derive ("operations",
323                                         "bind-initiated", opi, st);
324                         }
325                         else if (strcmp (dn,
326                                         "cn=UnBind,cn=Operations,cn=Monitor")
327                                         == 0)
328                         {
329                                 cldap_submit_derive ("operations",
330                                         "unbind-completed", opc, st);
331                                 cldap_submit_derive ("operations",
332                                         "unbind-initiated", opi, st);
333                         }
334                         else if (strcmp (dn,
335                                         "cn=Search,cn=Operations,cn=Monitor")
336                                         == 0)
337                         {
338                                 cldap_submit_derive ("operations",
339                                         "search-completed", opc, st);
340                                 cldap_submit_derive ("operations",
341                                         "search-initiated", opi, st);
342                         }
343                         else if (strcmp (dn,
344                                         "cn=Compare,cn=Operations,cn=Monitor")
345                                         == 0)
346                         {
347                                 cldap_submit_derive ("operations",
348                                         "compare-completed", opc, st);
349                                 cldap_submit_derive ("operations",
350                                         "compare-initiated", opi, st);
351                         }
352                         else if (strcmp (dn,
353                                         "cn=Modify,cn=Operations,cn=Monitor")
354                                         == 0)
355                         {
356                                 cldap_submit_derive ("operations",
357                                         "modify-completed", opc, st);
358                                 cldap_submit_derive ("operations",
359                                         "modify-initiated", opi, st);
360                         }
361                         else if (strcmp (dn,
362                                         "cn=Modrdn,cn=Operations,cn=Monitor")
363                                         == 0)
364                         {
365                                 cldap_submit_derive ("operations",
366                                         "modrdn-completed", opc, st);
367                                 cldap_submit_derive ("operations",
368                                         "modrdn-initiated", opi, st);
369                         }
370                         else if (strcmp (dn,
371                                         "cn=Add,cn=Operations,cn=Monitor")
372                                         == 0)
373                         {
374                                 cldap_submit_derive ("operations",
375                                         "add-completed", opc, st);
376                                 cldap_submit_derive ("operations",
377                                         "add-initiated", opi, st);
378                         }
379                         else if (strcmp (dn,
380                                         "cn=Delete,cn=Operations,cn=Monitor")
381                                         == 0)
382                         {
383                                 cldap_submit_derive ("operations",
384                                         "delete-completed", opc, st);
385                                 cldap_submit_derive ("operations",
386                                         "delete-initiated", opi, st);
387                         }
388                         else if (strcmp (dn,
389                                         "cn=Abandon,cn=Operations,cn=Monitor")
390                                         == 0)
391                         {
392                                 cldap_submit_derive ("operations",
393                                         "abandon-completed", opc, st);
394                                 cldap_submit_derive ("operations",
395                                         "abandon-initiated", opi, st);
396                         }
397                         else if (strcmp (dn,
398                                         "cn=Extended,cn=Operations,cn=Monitor")
399                                         == 0)
400                         {
401                                 cldap_submit_derive ("operations",
402                                         "extended-completed", opc, st);
403                                 cldap_submit_derive ("operations",
404                                         "extended-initiated", opi, st);
405                         }
406                         else if ((strncmp (dn, "cn=Database", 11) == 0)
407                                 && ((nc_list = ldap_get_values_len
408                                                 (st->ld, e, "namingContexts")) != NULL))
409                         {
410                                 nc_data = *nc_list[0];
411                                 char typeinst[DATA_MAX_NAME_LEN];
412
413                                 if ((olmbdb_list = ldap_get_values_len (st->ld, e,
414                                         "olmBDBEntryCache")) != NULL)
415                                 {
416                                         olmbdb_data = *olmbdb_list[0];
417                                         ssnprintf (typeinst, sizeof (typeinst),
418                                                 "bdbentrycache-%s", nc_data.bv_val);
419                                         cldap_submit_gauge ("cache_size", typeinst,
420                                                 atoll (olmbdb_data.bv_val), st);
421                                         ldap_value_free_len (olmbdb_list);
422                                 }
423
424                                 if ((olmbdb_list = ldap_get_values_len (st->ld, e,
425                                         "olmBDBDNCache")) != NULL)
426                                 {
427                                         olmbdb_data = *olmbdb_list[0];
428                                         ssnprintf (typeinst, sizeof (typeinst),
429                                                 "bdbdncache-%s", nc_data.bv_val);
430                                         cldap_submit_gauge ("cache_size", typeinst,
431                                                 atoll (olmbdb_data.bv_val), st);
432                                         ldap_value_free_len (olmbdb_list);
433                                 }
434
435                                 if ((olmbdb_list = ldap_get_values_len (st->ld, e,
436                                         "olmBDBIDLCache")) != NULL)
437                                 {
438                                         olmbdb_data = *olmbdb_list[0];
439                                         ssnprintf (typeinst, sizeof (typeinst),
440                                                 "bdbidlcache-%s", nc_data.bv_val);
441                                         cldap_submit_gauge ("cache_size", typeinst,
442                                                 atoll (olmbdb_data.bv_val), st);
443                                         ldap_value_free_len (olmbdb_list);
444                                 }
445
446                                 ldap_value_free_len (nc_list);
447                         }
448                         else if (strcmp (dn,
449                                         "cn=Bytes,cn=Statistics,cn=Monitor")
450                                         == 0)
451                         {
452                                 cldap_submit_derive ("derive", "statistics-bytes",
453                                         counter, st);
454                         }
455                         else if (strcmp (dn,
456                                         "cn=PDU,cn=Statistics,cn=Monitor")
457                                         == 0)
458                         {
459                                 cldap_submit_derive ("derive", "statistics-pdu",
460                                         counter, st);
461                         }
462                         else if (strcmp (dn,
463                                         "cn=Entries,cn=Statistics,cn=Monitor")
464                                         == 0)
465                         {
466                                 cldap_submit_derive ("derive", "statistics-entries",
467                                         counter, st);
468                         }
469                         else if (strcmp (dn,
470                                         "cn=Referrals,cn=Statistics,cn=Monitor")
471                                         == 0)
472                         {
473                                 cldap_submit_derive ("derive", "statistics-referrals",
474                                         counter, st);
475                         }
476                         else if (strcmp (dn,
477                                         "cn=Open,cn=Threads,cn=Monitor")
478                                         == 0)
479                         {
480                                 cldap_submit_gauge ("threads", "threads-open",
481                                         info, st);
482                         }
483                         else if (strcmp (dn,
484                                         "cn=Starting,cn=Threads,cn=Monitor")
485                                         == 0)
486                         {
487                                 cldap_submit_gauge ("threads", "threads-starting",
488                                         info, st);
489                         }
490                         else if (strcmp (dn,
491                                         "cn=Active,cn=Threads,cn=Monitor")
492                                         == 0)
493                         {
494                                 cldap_submit_gauge ("threads", "threads-active",
495                                         info, st);
496                         }
497                         else if (strcmp (dn,
498                                         "cn=Pending,cn=Threads,cn=Monitor")
499                                         == 0)
500                         {
501                                 cldap_submit_gauge ("threads", "threads-pending",
502                                         info, st);
503                         }
504                         else if (strcmp (dn,
505                                         "cn=Backload,cn=Threads,cn=Monitor")
506                                         == 0)
507                         {
508                                 cldap_submit_gauge ("threads", "threads-backload",
509                                         info, st);
510                         }
511                         else if (strcmp (dn,
512                                         "cn=Read,cn=Waiters,cn=Monitor")
513                                         == 0)
514                         {
515                                 cldap_submit_derive ("derive", "waiters-read",
516                                         counter, st);
517                         }
518                         else if (strcmp (dn,
519                                         "cn=Write,cn=Waiters,cn=Monitor")
520                                         == 0)
521                         {
522                                 cldap_submit_derive ("derive", "waiters-write",
523                                         counter, st);
524                         }
525
526                         ldap_value_free_len (counter_list);
527                         ldap_value_free_len (opc_list);
528                         ldap_value_free_len (opi_list);
529                         ldap_value_free_len (info_list);
530                 }
531
532                 ldap_memfree (dn);
533         }
534
535         ldap_msgfree (result);
536         ldap_unbind_ext_s (st->ld, NULL, NULL);
537         return (0);
538 } /* }}} int cldap_read_host */
539
540 /* Configuration handling functions {{{
541  *
542  * <Plugin ldap>
543  *   <Instance "plugin_instance1">
544  *     URL "ldap://localhost"
545  *     ...
546  *   </Instance>
547  * </Plugin>
548  */
549
550 static int cldap_config_add (oconfig_item_t *ci) /* {{{ */
551 {
552         cldap_t *st;
553         int i;
554         int status;
555
556         st = calloc (1, sizeof (*st));
557         if (st == NULL)
558         {
559                 ERROR ("openldap plugin: calloc failed.");
560                 return (-1);
561         }
562
563         status = cf_util_get_string (ci, &st->name);
564         if (status != 0)
565         {
566                 sfree (st);
567                 return (status);
568         }
569
570         st->starttls = 0;
571         st->timeout = -1;
572         st->verifyhost = 1;
573         st->version = LDAP_VERSION3;
574
575         for (i = 0; i < ci->children_num; i++)
576         {
577                 oconfig_item_t *child = ci->children + i;
578
579                 if (strcasecmp ("BindDN", child->key) == 0)
580                         status = cf_util_get_string (child, &st->binddn);
581                 else if (strcasecmp ("Password", child->key) == 0)
582                         status = cf_util_get_string (child, &st->password);
583                 else if (strcasecmp ("CACert", child->key) == 0)
584                         status = cf_util_get_string (child, &st->cacert);
585                 else if (strcasecmp ("StartTLS", child->key) == 0)
586                         status = cf_util_get_boolean (child, &st->starttls);
587                 else if (strcasecmp ("Timeout", child->key) == 0)
588                         status = cf_util_get_int (child, &st->timeout);
589                 else if (strcasecmp ("URL", child->key) == 0)
590                         status = cf_util_get_string (child, &st->url);
591                 else if (strcasecmp ("VerifyHost", child->key) == 0)
592                         status = cf_util_get_boolean (child, &st->verifyhost);
593                 else if (strcasecmp ("Version", child->key) == 0)
594                         status = cf_util_get_int (child, &st->version);
595                 else
596                 {
597                         WARNING ("openldap plugin: Option `%s' not allowed here.",
598                                         child->key);
599                         status = -1;
600                 }
601
602                 if (status != 0)
603                         break;
604         }
605
606         /* Check if struct is complete.. */
607         if ((status == 0) && (st->url == NULL))
608         {
609                 ERROR ("openldap plugin: Instance `%s': "
610                                 "No URL has been configured.",
611                                 st->name);
612                 status = -1;
613         }
614
615         /* Check if URL is valid */
616         if ((status == 0) && (st->url != NULL))
617         {
618                 LDAPURLDesc *ludpp;
619                 int rc;
620
621                 if ((rc = ldap_url_parse (st->url, &ludpp)) != 0)
622                 {
623                         ERROR ("openldap plugin: Instance `%s': "
624                                 "Invalid URL: `%s'",
625                                 st->name, st->url);
626                         status = -1;
627                 }
628
629                 if ((status == 0) && (ludpp->lud_host != NULL))
630                 {
631                         st->host = strdup (ludpp->lud_host);
632                 }
633
634                 ldap_free_urldesc (ludpp);
635         }
636
637         if (status == 0)
638         {
639                 user_data_t ud;
640                 char callback_name[3*DATA_MAX_NAME_LEN];
641
642                 memset (&ud, 0, sizeof (ud));
643                 ud.data = st;
644
645                 memset (callback_name, 0, sizeof (callback_name));
646                 ssnprintf (callback_name, sizeof (callback_name),
647                                 "openldap/%s/%s",
648                                 (st->host != NULL) ? st->host : hostname_g,
649                                 (st->name != NULL) ? st->name : "default"),
650
651                 status = plugin_register_complex_read (/* group = */ NULL,
652                                 /* name      = */ callback_name,
653                                 /* callback  = */ cldap_read_host,
654                                 /* interval  = */ 0,
655                                 /* user_data = */ &ud);
656         }
657
658         if (status != 0)
659         {
660                 cldap_free (st);
661                 return (-1);
662         }
663
664         return (0);
665 } /* }}} int cldap_config_add */
666
667 static int cldap_config (oconfig_item_t *ci) /* {{{ */
668 {
669         int i;
670         int status = 0;
671
672         for (i = 0; i < ci->children_num; i++)
673         {
674                 oconfig_item_t *child = ci->children + i;
675
676                 if (strcasecmp ("Instance", child->key) == 0)
677                         cldap_config_add (child);
678                 else
679                         WARNING ("openldap plugin: The configuration option "
680                                         "\"%s\" is not allowed here. Did you "
681                                         "forget to add an <Instance /> block "
682                                         "around the configuration?",
683                                         child->key);
684         } /* for (ci->children) */
685
686         return (status);
687 } /* }}} int cldap_config */
688
689 /* }}} End of configuration handling functions */
690
691 static int cldap_init (void) /* {{{ */
692 {
693         /* Initialize LDAP library while still single-threaded as recommended in
694          * ldap_initialize(3) */
695         int debug_level;
696         ldap_get_option (NULL, LDAP_OPT_DEBUG_LEVEL, &debug_level);
697         return (0);
698 } /* }}} int cldap_init */
699
700 void module_register (void) /* {{{ */
701 {
702         plugin_register_complex_config ("openldap", cldap_config);
703         plugin_register_init ("openldap", cldap_init);
704 } /* }}} void module_register */
705
706 #if defined(__APPLE__)
707 #pragma clang diagnostic pop
708 #endif