91a091f2e3f719f606f65f82041bf5cd6dcea672
[oweals/gnunet.git] / src / gns / plugin_rest_gns.c
1 /*
2    This file is part of GNUnet.
3    Copyright (C) 2012-2015 GNUnet e.V.
4
5    GNUnet is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published
7    by the Free Software Foundation; either version 3, or (at your
8    option) any later version.
9
10    GNUnet is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with GNUnet; see the file COPYING.  If not, write to the
17    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19    */
20 /**
21  * @author Martin Schanzenbach
22  * @file gns/plugin_rest_gns.c
23  * @brief GNUnet GNS REST plugin
24  *
25  */
26
27 #include "platform.h"
28 #include "gnunet_rest_plugin.h"
29 #include <gnunet_dnsparser_lib.h>
30 #include <gnunet_identity_service.h>
31 #include <gnunet_gnsrecord_lib.h>
32 #include <gnunet_namestore_service.h>
33 #include <gnunet_gns_service.h>
34 #include <gnunet_rest_lib.h>
35 #include <gnunet_jsonapi_lib.h>
36 #include <jansson.h>
37
38 #define GNUNET_REST_API_NS_GNS "/gns"
39
40 #define GNUNET_REST_JSONAPI_GNS_RECORD_TYPE "record_type"
41
42 #define GNUNET_REST_JSONAPI_GNS_TYPEINFO "gns_name"
43
44 #define GNUNET_REST_JSONAPI_GNS_RECORD "records"
45
46 #define GNUNET_REST_JSONAPI_GNS_EGO "ego"
47
48 #define GNUNET_REST_JSONAPI_GNS_PKEY "pkey"
49
50 #define GNUNET_REST_JSONAPI_GNS_OPTIONS "options"
51
52 /**
53  * @brief struct returned by the initialization function of the plugin
54  */
55 struct Plugin
56 {
57   const struct GNUNET_CONFIGURATION_Handle *cfg;
58 };
59
60 const struct GNUNET_CONFIGURATION_Handle *cfg;
61
62 struct LookupHandle
63 {
64   /**
65    * Handle to GNS service.
66    */
67   struct GNUNET_GNS_Handle *gns;
68
69   /**
70    * Desired timeout for the lookup (default is no timeout).
71    */
72   struct GNUNET_TIME_Relative timeout;
73
74   /**
75    * Handle to lookup request
76    */
77   struct GNUNET_GNS_LookupRequest *lookup_request;
78
79   /**
80    * Lookup an ego with the identity service.
81    */
82   struct GNUNET_IDENTITY_EgoLookup *el;
83
84   /**
85    * Handle for identity service.
86    */
87   struct GNUNET_IDENTITY_Handle *identity;
88
89   /**
90    * Active operation on identity service.
91    */
92   struct GNUNET_IDENTITY_Operation *id_op;
93
94   /**
95    * ID of a task associated with the resolution process.
96    */
97   struct GNUNET_SCHEDULER_Task * timeout_task;
98
99   /**
100    * The root of the received JSON or NULL
101    */
102   json_t *json_root;
103
104   /**
105    * The plugin result processor
106    */
107   GNUNET_REST_ResultProcessor proc;
108
109   /**
110    * The closure of the result processor
111    */
112   void *proc_cls;
113
114   /**
115    * The name to look up
116    */
117   char *name;
118
119   /**
120    * The ego to use
121    * In string representation from JSON
122    */
123   const char *ego_str;
124
125   /**
126    * The Pkey to use
127    * In string representation from JSON
128    */
129   const char *pkey_str;
130
131   /**
132    * The record type
133    */
134   int type;
135
136   /**
137    * The public key of to use for lookup
138    */
139   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
140
141   /**
142    * The public key to use for lookup
143    */
144   struct GNUNET_CRYPTO_EcdsaPublicKey pkeym;
145
146   /**
147    * The resolver options
148    */
149   enum GNUNET_GNS_LocalOptions options;
150
151   /**
152    * the shorten key
153    */
154   struct GNUNET_CRYPTO_EcdsaPrivateKey shorten_key;
155
156 };
157
158
159 /**
160  * Cleanup lookup handle.
161  *
162  * @param handle Handle to clean up
163  */
164 static void
165 cleanup_handle (struct LookupHandle *handle)
166 {
167   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
168               "Cleaning up\n");
169   if (NULL != handle->json_root)
170     json_decref (handle->json_root);
171
172   if (NULL != handle->name)
173     GNUNET_free (handle->name);
174   if (NULL != handle->el)
175   {
176     GNUNET_IDENTITY_ego_lookup_cancel (handle->el);
177     handle->el = NULL;
178   }
179   if (NULL != handle->id_op)
180   {
181     GNUNET_IDENTITY_cancel (handle->id_op);
182     handle->id_op = NULL;
183   }
184   if (NULL != handle->lookup_request)
185   {
186     GNUNET_GNS_lookup_cancel (handle->lookup_request);
187     handle->lookup_request = NULL;
188   }
189   if (NULL != handle->identity)
190   {
191     GNUNET_IDENTITY_disconnect (handle->identity);
192     handle->identity = NULL;
193   }
194   if (NULL != handle->gns)
195   {
196     GNUNET_GNS_disconnect (handle->gns);
197     handle->gns = NULL;
198   }
199
200   if (NULL != handle->timeout_task)
201   {
202     GNUNET_SCHEDULER_cancel (handle->timeout_task);
203   }
204   GNUNET_free (handle);
205 }
206
207
208 /**
209  * Task run on shutdown.  Cleans up everything.
210  *
211  * @param cls unused
212  * @param tc scheduler context
213  */
214 static void
215 do_error (void *cls)
216 {
217   struct LookupHandle *handle = cls;
218   struct MHD_Response *resp;
219
220   resp = GNUNET_REST_create_json_response (NULL);
221   handle->proc (handle->proc_cls, resp, MHD_HTTP_BAD_REQUEST);
222   cleanup_handle (handle);
223 }
224
225
226 /**
227  * Create json representation of a GNSRECORD
228  *
229  * @param rd the GNSRECORD_Data
230  */
231 static json_t *
232 gnsrecord_to_json (const struct GNUNET_GNSRECORD_Data *rd)
233 {
234   const char *typename;
235   char *string_val;
236   const char *exp_str;
237   json_t *record_obj;
238
239   typename = GNUNET_GNSRECORD_number_to_typename (rd->record_type);
240   string_val = GNUNET_GNSRECORD_value_to_string (rd->record_type,
241                                                  rd->data,
242                                                  rd->data_size);
243
244   if (NULL == string_val)
245   {
246     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
247                 "Record of type %d malformed, skipping\n",
248                 (int) rd->record_type);
249     return NULL;
250   }
251   record_obj = json_object();
252   json_object_set_new (record_obj, "type", json_string (typename));
253   json_object_set_new (record_obj, "value", json_string (string_val));
254   GNUNET_free (string_val);
255
256   if (GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION & rd->flags)
257   {
258     struct GNUNET_TIME_Relative time_rel;
259     time_rel.rel_value_us = rd->expiration_time;
260     exp_str = GNUNET_STRINGS_relative_time_to_string (time_rel, 1);
261   }
262   else
263   {
264     struct GNUNET_TIME_Absolute time_abs;
265     time_abs.abs_value_us = rd->expiration_time;
266     exp_str = GNUNET_STRINGS_absolute_time_to_string (time_abs);
267   }
268   json_object_set_new (record_obj, "expiration_time", json_string (exp_str));
269
270   json_object_set_new (record_obj, "expired",
271                        json_boolean (GNUNET_YES == GNUNET_GNSRECORD_is_expired (rd)));
272   return record_obj;
273 }
274
275 /**
276  * Function called with the result of a GNS lookup.
277  *
278  * @param cls the 'const char *' name that was resolved
279  * @param rd_count number of records returned
280  * @param rd array of @a rd_count records with the results
281  */
282 static void
283 process_lookup_result (void *cls, uint32_t rd_count,
284                        const struct GNUNET_GNSRECORD_Data *rd)
285 {
286   struct LookupHandle *handle = cls;
287   struct MHD_Response *resp;
288   struct GNUNET_JSONAPI_Object *json_object;
289   struct GNUNET_JSONAPI_Resource *json_resource;
290   uint32_t i;
291   char *result;
292   json_t *result_array;
293   json_t *record_obj;
294
295   result_array = json_array();
296   json_object = GNUNET_JSONAPI_object_new ();
297   json_resource = GNUNET_JSONAPI_resource_new (GNUNET_REST_JSONAPI_GNS_TYPEINFO, handle->name);
298   handle->lookup_request = NULL;
299   for (i=0; i<rd_count; i++)
300   {
301     if ( (rd[i].record_type != handle->type) &&
302          (GNUNET_GNSRECORD_TYPE_ANY != handle->type) )
303       continue;
304     record_obj = gnsrecord_to_json (&(rd[i]));
305     json_array_append (result_array, record_obj);
306     json_decref (record_obj);
307   }
308   GNUNET_JSONAPI_resource_add_attr (json_resource,
309                                          GNUNET_REST_JSONAPI_GNS_RECORD,
310                                          result_array);
311   GNUNET_JSONAPI_object_resource_add (json_object, json_resource);
312   GNUNET_JSONAPI_data_serialize (json_object, &result);
313   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result);
314   json_decref (result_array);
315   GNUNET_JSONAPI_object_delete (json_object);
316   resp = GNUNET_REST_create_json_response (result);
317   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
318   GNUNET_free (result);
319   cleanup_handle (handle);
320 }
321
322
323 /**
324  * Perform the actual resolution, starting with the zone
325  * identified by the given public key and the shorten zone.
326  *
327  * @param pkey public key to use for the zone, can be NULL
328  * @param shorten_key private key used for shortening, can be NULL
329  */
330 static void
331 lookup_with_keys (struct LookupHandle *handle, const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_key)
332 {
333   if (UINT32_MAX == handle->type)
334   {
335     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
336                 _("Invalid typename specified, assuming `ANY'\n"));
337     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
338   }
339   if (NULL != handle->name)
340   {
341     handle->lookup_request = GNUNET_GNS_lookup (handle->gns,
342                                                 handle->name,
343                                                 &handle->pkey,
344                                                 handle->type,
345                                                 handle->options,
346                                                 shorten_key,
347                                                 &process_lookup_result,
348                                                 handle);
349   }
350   else
351   {
352     GNUNET_SCHEDULER_add_now (&do_error, handle);
353     return;
354   }
355 }
356
357 /**
358  * Method called to with the ego we are to use for shortening
359  * during the lookup.
360  *
361  * @param cls closure contains the public key to use
362  * @param ego ego handle, NULL if not found
363  * @param ctx context for application to store data for this ego
364  *                 (during the lifetime of this process, initially NULL)
365  * @param name name assigned by the user for this ego,
366  *                   NULL if the user just deleted the ego and it
367  *                   must thus no longer be used
368  */
369 static void
370 identity_shorten_cb (void *cls,
371                      struct GNUNET_IDENTITY_Ego *ego,
372                      void **ctx,
373                      const char *name)
374 {
375   struct LookupHandle *handle = cls;
376
377   handle->id_op = NULL;
378   if (NULL == ego)
379     lookup_with_keys (handle, NULL);
380   else
381     lookup_with_keys (handle,
382                       GNUNET_IDENTITY_ego_get_private_key (ego));
383 }
384
385 /**
386  * Perform the actual resolution, starting with the zone
387  * identified by the given public key.
388  *
389  * @param pkey public key to use for the zone
390  */
391 static void
392 lookup_with_public_key (struct LookupHandle *handle)
393 {
394   handle->pkeym = handle->pkey;
395   GNUNET_break (NULL == handle->id_op);
396   handle->id_op = GNUNET_IDENTITY_get (handle->identity,
397                                        "gns-short",
398                                        &identity_shorten_cb,
399                                        handle);
400   if (NULL == handle->id_op)
401   {
402     GNUNET_break (0);
403     lookup_with_keys (handle, NULL);
404   }
405 }
406
407 /**
408  * Method called to with the ego we are to use for the lookup,
409  * when the ego is determined by a name.
410  *
411  * @param cls closure (NULL, unused)
412  * @param ego ego handle, NULL if not found
413  */
414 static void
415 identity_zone_cb (void *cls,
416                   const struct GNUNET_IDENTITY_Ego *ego)
417 {
418   struct LookupHandle *handle = cls;
419
420   handle->el = NULL;
421   if (NULL == ego)
422   {
423     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424                 _("Ego for not found, cannot perform lookup.\n"));
425     GNUNET_SCHEDULER_add_now (&do_error, handle);
426     return;
427   }
428   else
429   {
430     GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
431     lookup_with_public_key (handle);
432   }
433   json_decref(handle->json_root);
434 }
435
436 /**
437  * Method called to with the ego we are to use for the lookup,
438  * when the ego is the one for the default master zone.
439  *
440  * @param cls closure (NULL, unused)
441  * @param ego ego handle, NULL if not found
442  * @param ctx context for application to store data for this ego
443  *                 (during the lifetime of this process, initially NULL)
444  * @param name name assigned by the user for this ego,
445  *                   NULL if the user just deleted the ego and it
446  *                   must thus no longer be used
447  */
448 static void
449 identity_master_cb (void *cls,
450                     struct GNUNET_IDENTITY_Ego *ego,
451                     void **ctx,
452                     const char *name)
453 {
454   const char *dot;
455   struct LookupHandle *handle = cls;
456
457   handle->id_op = NULL;
458   if (NULL == ego)
459   {
460     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
461                 _("Ego for `gns-master' not found, cannot perform lookup.  Did you run gnunet-gns-import.sh?\n"));
462     GNUNET_SCHEDULER_add_now (&do_error, handle);
463     return;
464   }
465   GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
466   /* main name is our own master zone, do no look for that in the DHT */
467   handle->options = GNUNET_GNS_LO_LOCAL_MASTER;
468   /* if the name is of the form 'label.gnu', never go to the DHT */
469   dot = NULL;
470   if (NULL != handle->name)
471     dot = strchr (handle->name, '.');
472   if ( (NULL != dot) &&
473        (0 == strcasecmp (dot, ".gnu")) )
474     handle->options = GNUNET_GNS_LO_NO_DHT;
475   lookup_with_public_key (handle);
476 }
477
478 /**
479  * Parse REST uri for name and record type
480  *
481  * @param url Url to parse
482  * @param handle lookup handle to populate
483  * @return GNUNET_SYSERR on error
484  */
485 static int
486 parse_url (const char *url, struct LookupHandle *handle)
487 {
488   char *name;
489   char tmp_url[strlen(url)+1];
490   char *tok;
491
492   strcpy (tmp_url, url);
493   tok = strtok ((char*)tmp_url, "/");
494   if (NULL == tok)
495     return GNUNET_SYSERR;
496   name = strtok (NULL, "/");
497   if (NULL == name)
498     return GNUNET_SYSERR;
499   GNUNET_asprintf (&handle->name,
500                    "%s",
501                    name);
502   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
503               "Got name: %s\n", handle->name);
504   return GNUNET_OK;
505 }
506
507 static void
508 get_gns_cont (struct RestConnectionDataHandle *conndata_handle,
509               const char* url,
510               void *cls)
511 {
512   struct LookupHandle *handle = cls;
513   struct GNUNET_HashCode key;
514
515   //parse name and type from url
516   if (GNUNET_OK != parse_url (url, handle))
517   {
518     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing url...\n");
519     GNUNET_SCHEDULER_add_now (&do_error, handle);
520     return;
521   }
522   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
523               "Connecting...\n");
524   handle->gns = GNUNET_GNS_connect (cfg);
525   handle->identity = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
526   handle->timeout_task = GNUNET_SCHEDULER_add_delayed (handle->timeout,
527                                                        &do_error, handle);
528   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
529               "Connected\n");
530   if (NULL == handle->gns)
531   {
532     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
533                 "Connecting to GNS failed\n");
534     GNUNET_SCHEDULER_add_now (&do_error, handle);
535     return;
536   }
537   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_OPTIONS,
538                       strlen (GNUNET_REST_JSONAPI_GNS_OPTIONS),
539                       &key);
540   handle->options = GNUNET_GNS_LO_DEFAULT;
541   if ( GNUNET_YES ==
542        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
543                                                &key) )
544   {
545     handle->options = GNUNET_GNS_LO_DEFAULT;//TODO(char*) GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
546     //&key);
547   }
548   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE,
549                       strlen (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE),
550                       &key);
551   if ( GNUNET_YES ==
552        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
553                                                &key) )
554   {
555     handle->type = GNUNET_GNSRECORD_typename_to_number
556       (GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
557                                           &key));
558   }
559   else
560     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
561
562   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_PKEY,
563                       strlen (GNUNET_REST_JSONAPI_GNS_PKEY),
564                       &key);
565   if ( GNUNET_YES ==
566        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
567                                                &key) )
568   {
569     handle->pkey_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
570                                                           &key);
571     if (GNUNET_OK !=
572         GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->pkey_str,
573                                                     strlen(handle->pkey_str),
574                                                     &(handle->pkey)))
575     {
576       GNUNET_SCHEDULER_add_now (&do_error, handle);
577       return;
578     }
579     lookup_with_public_key (handle);
580     return;
581   }
582   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_EGO,
583                       strlen (GNUNET_REST_JSONAPI_GNS_EGO),
584                       &key);
585   if ( GNUNET_YES ==
586        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
587                                                &key) )
588   {
589     handle->ego_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
590                                                          &key);
591     handle->el = GNUNET_IDENTITY_ego_lookup (cfg,
592                                              handle->ego_str,
593                                              &identity_zone_cb,
594                                              handle);
595     return;
596   }
597   if ( (NULL != handle->name) &&
598        (strlen (handle->name) > 4) &&
599        (0 == strcmp (".zkey",
600                      &handle->name[strlen (handle->name) - 4])) )
601   {
602     GNUNET_CRYPTO_ecdsa_key_get_public
603       (GNUNET_CRYPTO_ecdsa_key_get_anonymous (),
604        &(handle->pkey));
605     lookup_with_public_key (handle);
606   }
607   else
608   {
609     GNUNET_break (NULL == handle->id_op);
610     handle->id_op = GNUNET_IDENTITY_get (handle->identity,
611                                          "gns-master",
612                                          &identity_master_cb,
613                                          handle);
614     GNUNET_assert (NULL != handle->id_op);
615   }
616 }
617
618 /**
619  * Handle rest request
620  *
621  * @param handle the lookup handle
622  */
623 static void
624 options_cont (struct RestConnectionDataHandle *con_handle,
625               const char* url,
626               void *cls)
627 {
628   struct MHD_Response *resp;
629   struct LookupHandle *handle = cls;
630
631   //For GNS, independent of path return all options
632   resp = GNUNET_REST_create_json_response (NULL);
633   MHD_add_response_header (resp,
634                            "Access-Control-Allow-Methods",
635                            MHD_HTTP_METHOD_GET);
636   handle->proc (handle->proc_cls,
637                 resp,
638                 MHD_HTTP_OK);
639   cleanup_handle (handle);
640 }
641
642
643 /**
644  * Function processing the REST call
645  *
646  * @param method HTTP method
647  * @param url URL of the HTTP request
648  * @param data body of the HTTP request (optional)
649  * @param data_size length of the body
650  * @param proc callback function for the result
651  * @param proc_cls closure for callback function
652  * @return GNUNET_OK if request accepted
653  */
654 static void
655 rest_gns_process_request(struct RestConnectionDataHandle *conndata_handle,
656                          GNUNET_REST_ResultProcessor proc,
657                          void *proc_cls)
658 {
659   struct LookupHandle *handle = GNUNET_new (struct LookupHandle);
660
661   handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
662   handle->proc_cls = proc_cls;
663   handle->proc = proc;
664
665   static const struct GNUNET_REST_RestConnectionHandler handlers[] = {
666     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_GNS, &get_gns_cont},
667     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_GNS, &options_cont},
668     GNUNET_REST_HANDLER_END
669   };
670
671   if (GNUNET_NO == GNUNET_REST_handle_request (conndata_handle, handlers, handle))
672     GNUNET_SCHEDULER_add_now (&do_error, handle);
673 }
674
675
676 /**
677  * Entry point for the plugin.
678  *
679  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
680  * @return NULL on error, otherwise the plugin context
681  */
682 void *
683 libgnunet_plugin_rest_gns_init (void *cls)
684 {
685   static struct Plugin plugin;
686   cfg = cls;
687   struct GNUNET_REST_Plugin *api;
688
689   if (NULL != plugin.cfg)
690     return NULL;                /* can only initialize once! */
691   memset (&plugin, 0, sizeof (struct Plugin));
692   plugin.cfg = cfg;
693   api = GNUNET_new (struct GNUNET_REST_Plugin);
694   api->cls = &plugin;
695   api->name = GNUNET_REST_API_NS_GNS;
696   api->process_request = &rest_gns_process_request;
697   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
698               _("GNS REST API initialized\n"));
699   return api;
700 }
701
702
703 /**
704  * Exit point from the plugin.
705  *
706  * @param cls the plugin context (as returned by "init")
707  * @return always NULL
708  */
709 void *
710 libgnunet_plugin_rest_gns_done (void *cls)
711 {
712   struct GNUNET_REST_Plugin *api = cls;
713   struct Plugin *plugin = api->cls;
714
715   plugin->cfg = NULL;
716   GNUNET_free (api);
717   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
718               "GNS REST plugin is finished\n");
719   return NULL;
720 }
721
722 /* end of plugin_rest_gns.c */