removing unused "RF_PENDING" flag
[oweals/gnunet.git] / src / gnsrecord / plugin_gnsrecord_dns.c
1 /*
2      This file is part of GNUnet
3      (C) 2013, 2014 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file gnsrecord/plugin_gnsrecord_dns.c
23  * @brief gnsrecord plugin to provide the API for basic DNS records
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_dnsparser_lib.h"
29 #include "gnunet_gnsrecord_plugin.h"
30
31
32 /**
33  * Convert the 'value' of a record to a string.
34  *
35  * @param cls closure, unused
36  * @param type type of the record
37  * @param data value in binary encoding
38  * @param data_size number of bytes in @a data
39  * @return NULL on error, otherwise human-readable representation of the value
40  */
41 static char *
42 dns_value_to_string (void *cls,
43                      uint32_t type,
44                      const void *data,
45                      size_t data_size)
46 {
47   char* result;
48   char tmp[INET6_ADDRSTRLEN];
49
50   switch (type)
51   {
52   case GNUNET_DNSPARSER_TYPE_A:
53     if (data_size != sizeof (struct in_addr))
54       return NULL;
55     if (NULL == inet_ntop (AF_INET, data, tmp, sizeof (tmp)))
56       return NULL;
57     return GNUNET_strdup (tmp);
58   case GNUNET_DNSPARSER_TYPE_NS:
59     {
60       char *ns;
61       size_t off;
62
63       off = 0;
64       ns = GNUNET_DNSPARSER_parse_name (data,
65                                         data_size,
66                                         &off);
67       if ( (NULL == ns) ||
68            (off != data_size) )
69       {
70         GNUNET_break_op (0);
71         GNUNET_free_non_null (ns);
72         return NULL;
73       }
74       return ns;
75     }
76   case GNUNET_DNSPARSER_TYPE_CNAME:
77     {
78       char *cname;
79       size_t off;
80
81       off = 0;
82       cname = GNUNET_DNSPARSER_parse_name (data,
83                                            data_size,
84                                            &off);
85       if ( (NULL == cname) ||
86            (off != data_size) )
87       {
88         GNUNET_break_op (0);
89         GNUNET_free_non_null (cname);
90         return NULL;
91       }
92       return cname;
93     }
94   case GNUNET_DNSPARSER_TYPE_SOA:
95     {
96       struct GNUNET_DNSPARSER_SoaRecord *soa;
97       size_t off;
98
99       off = 0;
100       soa = GNUNET_DNSPARSER_parse_soa (data,
101                                         data_size,
102                                         &off);
103       if ( (NULL == soa) ||
104            (off != data_size) )
105       {
106         GNUNET_break_op (0);
107         if (NULL != soa)
108           GNUNET_DNSPARSER_free_soa (soa);
109         return NULL;
110       }
111       GNUNET_asprintf (&result,
112                        "rname=%s mname=%s %lu,%lu,%lu,%lu,%lu",
113                        soa->rname,
114                        soa->mname,
115                        soa->serial,
116                        soa->refresh,
117                        soa->retry,
118                        soa->expire,
119                        soa->minimum_ttl);
120       GNUNET_DNSPARSER_free_soa (soa);
121       return result;
122     }
123   case GNUNET_DNSPARSER_TYPE_PTR:
124     {
125       char *ptr;
126       size_t off;
127
128       off = 0;
129       ptr = GNUNET_DNSPARSER_parse_name (data,
130                                            data_size,
131                                            &off);
132       if ( (NULL == ptr) ||
133            (off != data_size) )
134       {
135         GNUNET_break_op (0);
136         GNUNET_free_non_null (ptr);
137         return NULL;
138       }
139       return ptr;
140     }
141   case GNUNET_DNSPARSER_TYPE_CERT:
142     {
143       struct GNUNET_DNSPARSER_CertRecord *cert;
144       size_t off;
145       char *base64;
146       int len;
147
148       off = 0;
149       cert = GNUNET_DNSPARSER_parse_cert (data,
150                                           data_size,
151                                           &off);
152       if ( (NULL == cert) ||
153            (off != data_size) )
154       {
155         GNUNET_break_op (0);
156         GNUNET_DNSPARSER_free_cert (cert);
157         return NULL;
158       }
159       len = GNUNET_STRINGS_base64_encode (cert->certificate_data,
160                                           cert->certificate_size,
161                                           &base64);
162       GNUNET_asprintf (&result,
163                        "%u %u %u %.*s",
164                        cert->cert_type,
165                        cert->cert_tag,
166                        cert->algorithm,
167                        len,
168                        base64);
169       GNUNET_free (base64);
170       GNUNET_DNSPARSER_free_cert (cert);
171       return result;
172     }
173   case GNUNET_DNSPARSER_TYPE_MX:
174     {
175       struct GNUNET_DNSPARSER_MxRecord *mx;
176       size_t off;
177
178       off = 0;
179       mx = GNUNET_DNSPARSER_parse_mx (data,
180                                       data_size,
181                                       &off);
182       if ( (NULL == mx) ||
183            (off != data_size) )
184       {
185         GNUNET_break_op (0);
186         GNUNET_DNSPARSER_free_mx (mx);
187         return NULL;
188       }
189       GNUNET_asprintf (&result,
190                        "%u,%s",
191                        (unsigned int) mx->preference,
192                        mx->mxhost);
193       GNUNET_DNSPARSER_free_mx (mx);
194       return result;
195     }
196   case GNUNET_DNSPARSER_TYPE_TXT:
197     return GNUNET_strndup (data, data_size);
198   case GNUNET_DNSPARSER_TYPE_AAAA:
199     if (data_size != sizeof (struct in6_addr))
200       return NULL;
201     if (NULL == inet_ntop (AF_INET6, data, tmp, sizeof (tmp)))
202       return NULL;
203     return GNUNET_strdup (tmp);
204   case GNUNET_DNSPARSER_TYPE_SRV:
205     {
206       struct GNUNET_DNSPARSER_SrvRecord *srv;
207       size_t off;
208
209       off = 0;
210       srv = GNUNET_DNSPARSER_parse_srv (data,
211                                         data_size,
212                                         &off);
213       if ( (NULL == srv) ||
214            (off != data_size) )
215       {
216         GNUNET_break_op (0);
217         if (NULL != srv)
218           GNUNET_DNSPARSER_free_srv (srv);
219         return NULL;
220       }
221       GNUNET_asprintf (&result,
222                        "%d %d %d %s",
223                        srv->priority,
224                        srv->weight,
225                        srv->port,
226                        srv->target);
227       GNUNET_DNSPARSER_free_srv (srv);
228       return result;
229     }
230   case GNUNET_DNSPARSER_TYPE_TLSA:
231     {
232       const struct GNUNET_TUN_DnsTlsaRecord *tlsa;
233       char *tlsa_str;
234       char *hex;
235
236       if (data_size < sizeof (struct GNUNET_TUN_DnsTlsaRecord))
237         return NULL; /* malformed */
238       tlsa = data;
239       hex = GNUNET_DNSPARSER_bin_to_hex (&tlsa[1],
240                                          data_size - sizeof (struct GNUNET_TUN_DnsTlsaRecord));
241       if (0 == GNUNET_asprintf (&tlsa_str,
242                                 "%u %u %u %s",
243                                 (unsigned int) tlsa->usage,
244                                 (unsigned int) tlsa->selector,
245                                 (unsigned int) tlsa->matching_type,
246                                 hex))
247       {
248         GNUNET_free (hex);
249         GNUNET_free (tlsa_str);
250         return NULL;
251       }
252       GNUNET_free (hex);
253       return tlsa_str;
254     }
255   default:
256     return NULL;
257   }
258 }
259
260
261 /**
262  * Convert human-readable version of a 'value' of a record to the binary
263  * representation.
264  *
265  * @param cls closure, unused
266  * @param type type of the record
267  * @param s human-readable string
268  * @param data set to value in binary encoding (will be allocated)
269  * @param data_size set to number of bytes in @a data
270  * @return #GNUNET_OK on success
271  */
272 static int
273 dns_string_to_value (void *cls,
274                      uint32_t type,
275                      const char *s,
276                      void **data,
277                      size_t *data_size)
278 {
279   struct in_addr value_a;
280   struct in6_addr value_aaaa;
281   struct GNUNET_TUN_DnsTlsaRecord *tlsa;
282
283   if (NULL == s)
284     return GNUNET_SYSERR;
285   switch (type)
286   {
287   case GNUNET_DNSPARSER_TYPE_A:
288     if (1 != inet_pton (AF_INET, s, &value_a))
289     {
290       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
291                   _("Unable to parse IPv4 address `%s'\n"),
292                   s);
293       return GNUNET_SYSERR;
294     }
295     *data = GNUNET_new (struct in_addr);
296     memcpy (*data, &value_a, sizeof (value_a));
297     *data_size = sizeof (value_a);
298     return GNUNET_OK;
299   case GNUNET_DNSPARSER_TYPE_NS:
300     {
301       char nsbuf[256];
302       size_t off;
303
304       off = 0;
305       if (GNUNET_OK !=
306           GNUNET_DNSPARSER_builder_add_name (nsbuf,
307                                              sizeof (nsbuf),
308                                              &off,
309                                              s))
310       {
311         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
312              _("Failed to serialize NS record with value `%s'\n"),
313              s);
314         return GNUNET_SYSERR;
315       }
316       *data_size = off;
317       *data = GNUNET_malloc (off);
318       memcpy (*data, nsbuf, off);
319       return GNUNET_OK;
320     }
321   case GNUNET_DNSPARSER_TYPE_CNAME:
322     {
323       char cnamebuf[256];
324       size_t off;
325
326       off = 0;
327       if (GNUNET_OK !=
328           GNUNET_DNSPARSER_builder_add_name (cnamebuf,
329                                              sizeof (cnamebuf),
330                                              &off,
331                                              s))
332       {
333         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
334              _("Failed to serialize CNAME record with value `%s'\n"),
335              s);
336         return GNUNET_SYSERR;
337       }
338       *data_size = off;
339       *data = GNUNET_malloc (off);
340       memcpy (*data, cnamebuf, off);
341       return GNUNET_OK;
342     }
343   case GNUNET_DNSPARSER_TYPE_CERT:
344     {
345       char *sdup;
346       const char *typep;
347       const char *keyp;
348       const char *algp;
349       const char *certp;
350       unsigned int type;
351       unsigned int key;
352       unsigned int alg;
353       size_t cert_size;
354       char *cert_data;
355       struct GNUNET_DNSPARSER_CertRecord cert;
356
357       sdup = GNUNET_strdup (s);
358       typep = strtok (sdup, " ");
359       /* TODO: add typep mnemonic conversion according to RFC 4398 */
360       if ( (NULL == typep) ||
361            (1 != SSCANF (typep,
362                          "%u",
363                          &type)) ||
364            (type > UINT16_MAX) )
365       {
366         GNUNET_free (sdup);
367         return GNUNET_SYSERR;
368       }
369       keyp = strtok (NULL, " ");
370       if ( (NULL == keyp) ||
371            (1 != SSCANF (keyp,
372                          "%u",
373                          &key)) ||
374            (key > UINT16_MAX) )
375       {
376         GNUNET_free (sdup);
377         return GNUNET_SYSERR;
378       }
379       algp = strtok (NULL, " ");
380       /* TODO: add algp mnemonic conversion according to RFC 4398/RFC 4034 */
381       if ( (NULL == algp) ||
382            (1 != sscanf (algp,
383                          "%u",
384                          &alg)) ||
385            (alg > UINT8_MAX) )
386       {
387         GNUNET_free (sdup);
388         return GNUNET_SYSERR;
389       }
390       certp = strtok (NULL, " ");
391       if ( (NULL == certp) ||
392            (0 == strlen (certp) ) )
393       {
394         GNUNET_free (sdup);
395         return GNUNET_SYSERR;
396       }
397       cert_size = GNUNET_STRINGS_base64_decode (certp,
398                                                 strlen (certp),
399                                                 &cert_data);
400       GNUNET_free (sdup);
401       cert.cert_type = type;
402       cert.cert_tag = key;
403       cert.algorithm = alg;
404       cert.certificate_size = cert_size;
405       cert.certificate_data = cert_data;
406       {
407         char certbuf[cert_size + sizeof (struct GNUNET_TUN_DnsCertRecord)];
408         size_t off;
409
410         off = 0;
411         if (GNUNET_OK !=
412             GNUNET_DNSPARSER_builder_add_cert (certbuf,
413                                                sizeof (certbuf),
414                                                &off,
415                                                &cert))
416         {
417           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
418                       _("Failed to serialize CERT record with %u bytes\n"),
419                       (unsigned int) cert_size);
420           GNUNET_free (cert_data);
421           return GNUNET_SYSERR;
422         }
423         GNUNET_free (cert_data);
424         *data_size = off;
425         *data = GNUNET_malloc (off);
426         memcpy (*data, certbuf, off);
427       }
428       GNUNET_free (cert_data);
429       return GNUNET_OK;
430     }
431   case GNUNET_DNSPARSER_TYPE_SOA:
432     {
433       struct GNUNET_DNSPARSER_SoaRecord soa;
434       char soabuf[540];
435       char soa_rname[253 + 1];
436       char soa_mname[253 + 1];
437       unsigned int soa_serial;
438       unsigned int soa_refresh;
439       unsigned int soa_retry;
440       unsigned int soa_expire;
441       unsigned int soa_min;
442       size_t off;
443
444       if (7 != SSCANF (s,
445                        "rname=%253s mname=%253s %u,%u,%u,%u,%u",
446                        soa_rname,
447                        soa_mname,
448                        &soa_serial,
449                        &soa_refresh,
450                        &soa_retry,
451                        &soa_expire,
452                        &soa_min))
453       {
454         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
455              _("Unable to parse SOA record `%s'\n"),
456              s);
457         return GNUNET_SYSERR;
458       }
459       soa.mname = soa_mname;
460       soa.rname = soa_rname;
461       soa.serial = (uint32_t) soa_serial;
462       soa.refresh =(uint32_t)  soa_refresh;
463       soa.retry = (uint32_t) soa_retry;
464       soa.expire = (uint32_t) soa_expire;
465       soa.minimum_ttl = (uint32_t) soa_min;
466       off = 0;
467       if (GNUNET_OK !=
468           GNUNET_DNSPARSER_builder_add_soa (soabuf,
469                                             sizeof (soabuf),
470                                             &off,
471                                             &soa))
472       {
473         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
474                     _("Failed to serialize SOA record with mname `%s' and rname `%s'\n"),
475                     soa_mname,
476                     soa_rname);
477         return GNUNET_SYSERR;
478       }
479       *data_size = off;
480       *data = GNUNET_malloc (off);
481       memcpy (*data, soabuf, off);
482       return GNUNET_OK;
483     }
484   case GNUNET_DNSPARSER_TYPE_PTR:
485     {
486       char ptrbuf[256];
487       size_t off;
488
489       off = 0;
490       if (GNUNET_OK !=
491           GNUNET_DNSPARSER_builder_add_name (ptrbuf,
492                                              sizeof (ptrbuf),
493                                              &off,
494                                              s))
495       {
496         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
497                     _("Failed to serialize PTR record with value `%s'\n"),
498                     s);
499         return GNUNET_SYSERR;
500       }
501       *data_size = off;
502       *data = GNUNET_malloc (off);
503       memcpy (*data, ptrbuf, off);
504       return GNUNET_OK;
505     }
506   case GNUNET_DNSPARSER_TYPE_MX:
507     {
508       struct GNUNET_DNSPARSER_MxRecord mx;
509       char mxbuf[258];
510       char mxhost[253 + 1];
511       unsigned int mx_pref;
512       size_t off;
513
514       if (2 != SSCANF(s,
515                       "%u,%253s",
516                       &mx_pref,
517                       mxhost))
518       {
519         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
520              _("Unable to parse MX record `%s'\n"),
521              s);
522       return GNUNET_SYSERR;
523       }
524       mx.preference = (uint16_t) mx_pref;
525       mx.mxhost = mxhost;
526       off = 0;
527
528       if (GNUNET_OK !=
529           GNUNET_DNSPARSER_builder_add_mx (mxbuf,
530                                            sizeof (mxbuf),
531                                            &off,
532                                            &mx))
533       {
534         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
535              _("Failed to serialize MX record with hostname `%s'\n"),
536              mxhost);
537         return GNUNET_SYSERR;
538       }
539       *data_size = off;
540       *data = GNUNET_malloc (off);
541       memcpy (*data, mxbuf, off);
542       return GNUNET_OK;
543     }
544   case GNUNET_DNSPARSER_TYPE_SRV:
545     {
546       struct GNUNET_DNSPARSER_SrvRecord srv;
547       char srvbuf[270];
548       char srvtarget[253 + 1];
549       unsigned int priority;
550       unsigned int weight;
551       unsigned int port;
552       size_t off;
553
554       if (4 != SSCANF(s,
555                       "%u %u %u %253s",
556                       &priority,
557                       &weight,
558                       &port,
559                       srvtarget))
560       {
561         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
562              _("Unable to parse SRV record `%s'\n"),
563              s);
564         return GNUNET_SYSERR;
565       }
566       srv.priority = (uint16_t) priority;
567       srv.weight = (uint16_t) weight;
568       srv.port = (uint16_t) port;
569       srv.target = srvtarget;
570       off = 0;
571       if (GNUNET_OK !=
572           GNUNET_DNSPARSER_builder_add_srv (srvbuf,
573                                             sizeof (srvbuf),
574                                             &off,
575                                             &srv))
576       {
577         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
578                     _("Failed to serialize SRV record with target `%s'\n"),
579                     srvtarget);
580         return GNUNET_SYSERR;
581       }
582       *data_size = off;
583       *data = GNUNET_malloc (off);
584       memcpy (*data, srvbuf, off);
585       return GNUNET_OK;
586     }
587   case GNUNET_DNSPARSER_TYPE_TXT:
588     *data = GNUNET_strdup (s);
589     *data_size = strlen (s);
590     return GNUNET_OK;
591   case GNUNET_DNSPARSER_TYPE_AAAA:
592     if (1 != inet_pton (AF_INET6, s, &value_aaaa))
593     {
594       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
595            _("Unable to parse IPv6 address `%s'\n"),
596            s);
597       return GNUNET_SYSERR;
598     }
599     *data = GNUNET_new (struct in6_addr);
600     *data_size = sizeof (struct in6_addr);
601     memcpy (*data, &value_aaaa, sizeof (value_aaaa));
602     return GNUNET_OK;
603   case GNUNET_DNSPARSER_TYPE_TLSA:
604     {
605       unsigned int usage;
606       unsigned int selector;
607       unsigned int matching_type;
608       size_t slen = strlen (s) + 1;
609       char hex[slen];
610
611       if (4 != SSCANF (s,
612                        "%u %u %u %s",
613                        &usage,
614                        &selector,
615                        &matching_type,
616                        hex))
617       {
618         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
619                     _("Unable to parse TLSA record string `%s'\n"),
620                     s);
621         *data_size = 0;
622         return GNUNET_SYSERR;
623       }
624
625       *data_size = sizeof (struct GNUNET_TUN_DnsTlsaRecord) + strlen (hex) / 2;
626       *data = tlsa = GNUNET_malloc (*data_size);
627       tlsa->usage = (uint8_t) usage;
628       tlsa->selector = (uint8_t) selector;
629       tlsa->matching_type = (uint8_t) matching_type;
630       if (strlen (hex) / 2 !=
631           GNUNET_DNSPARSER_hex_to_bin (hex,
632                                        &tlsa[1]))
633       {
634         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
635                     _("Unable to parse TLSA record string `%s'\n"),
636                     s);
637         GNUNET_free (*data);
638         *data = NULL;
639         *data_size = 0;
640         return GNUNET_SYSERR;
641       }
642       return GNUNET_OK;
643     }
644   default:
645     return GNUNET_SYSERR;
646   }
647 }
648
649
650 /**
651  * Mapping of record type numbers to human-readable
652  * record type names.
653  */
654 static struct {
655   const char *name;
656   uint32_t number;
657 } name_map[] = {
658   { "A", GNUNET_DNSPARSER_TYPE_A },
659   { "NS", GNUNET_DNSPARSER_TYPE_NS },
660   { "CNAME", GNUNET_DNSPARSER_TYPE_CNAME },
661   { "SOA", GNUNET_DNSPARSER_TYPE_SOA },
662   { "PTR", GNUNET_DNSPARSER_TYPE_PTR },
663   { "MX", GNUNET_DNSPARSER_TYPE_MX },
664   { "TXT", GNUNET_DNSPARSER_TYPE_TXT },
665   { "AAAA", GNUNET_DNSPARSER_TYPE_AAAA },
666   { "SRV", GNUNET_DNSPARSER_TYPE_SRV },
667   { "TLSA", GNUNET_DNSPARSER_TYPE_TLSA },
668   { "CERT", GNUNET_DNSPARSER_TYPE_CERT },
669   { NULL, UINT32_MAX }
670 };
671
672
673 /**
674  * Convert a type name (i.e. "AAAA") to the corresponding number.
675  *
676  * @param cls closure, unused
677  * @param dns_typename name to convert
678  * @return corresponding number, UINT32_MAX on error
679  */
680 static uint32_t
681 dns_typename_to_number (void *cls,
682                         const char *dns_typename)
683 {
684   unsigned int i;
685
686   i=0;
687   while ( (NULL != name_map[i].name) &&
688           (0 != strcasecmp (dns_typename, name_map[i].name)) )
689     i++;
690   return name_map[i].number;
691 }
692
693
694 /**
695  * Convert a type number (i.e. 1) to the corresponding type string (i.e. "A")
696  *
697  * @param cls closure, unused
698  * @param type number of a type to convert
699  * @return corresponding typestring, NULL on error
700  */
701 static const char *
702 dns_number_to_typename (void *cls,
703                         uint32_t type)
704 {
705   unsigned int i;
706
707   i=0;
708   while ( (NULL != name_map[i].name) &&
709           (type != name_map[i].number) )
710     i++;
711   return name_map[i].name;
712 }
713
714
715 /**
716  * Entry point for the plugin.
717  *
718  * @param cls NULL
719  * @return the exported block API
720  */
721 void *
722 libgnunet_plugin_gnsrecord_dns_init (void *cls)
723 {
724   struct GNUNET_GNSRECORD_PluginFunctions *api;
725
726   api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
727   api->value_to_string = &dns_value_to_string;
728   api->string_to_value = &dns_string_to_value;
729   api->typename_to_number = &dns_typename_to_number;
730   api->number_to_typename = &dns_number_to_typename;
731   return api;
732 }
733
734
735 /**
736  * Exit point from the plugin.
737  *
738  * @param cls the return value from #libgnunet_plugin_block_test_init
739  * @return NULL
740  */
741 void *
742 libgnunet_plugin_gnsrecord_dns_done (void *cls)
743 {
744   struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
745
746   GNUNET_free (api);
747   return NULL;
748 }
749
750 /* end of plugin_gnsrecord_dns.c */