OpenDNSSEC-signer  2.1.10
confparser.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2009 NLNet Labs. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
19  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
21  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
23  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  */
26 
32 #include "config.h"
33 #include "compat.h"
34 #include "parser/confparser.h"
35 #include "parser/zonelistparser.h"
36 #include "log.h"
37 #include "status.h"
38 #include "wire/acl.h"
39 
40 #include <libxml/xpath.h>
41 #include <libxml/relaxng.h>
42 #include <libxml/xmlreader.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <sys/un.h>
46 
47 static const char* parser_str = "parser";
48 
49 
54 ods_status
55 parse_file_check(const char* cfgfile, const char* rngfile)
56 {
57  xmlDocPtr doc = NULL;
58  xmlDocPtr rngdoc = NULL;
59  xmlRelaxNGParserCtxtPtr rngpctx = NULL;
60  xmlRelaxNGValidCtxtPtr rngctx = NULL;
61  xmlRelaxNGPtr schema = NULL;
62  int status = 0;
63 
64  if (!cfgfile || !rngfile) {
65  return ODS_STATUS_ASSERT_ERR;
66  }
67  ods_log_debug("[%s] check cfgfile %s with rngfile %s", parser_str,
68  cfgfile, rngfile);
69  /* Load XML document */
70  doc = xmlParseFile(cfgfile);
71  if (doc == NULL) {
72  ods_log_error("[%s] unable to parse file: failed to load cfgfile %s",
73  parser_str, cfgfile);
74  return ODS_STATUS_XML_ERR;
75  }
76  /* Load rng document */
77  rngdoc = xmlParseFile(rngfile);
78  if (rngdoc == NULL) {
79  ods_log_error("[%s] unable to parse file: failed to load rngfile %s",
80  parser_str, rngfile);
81  xmlFreeDoc(doc);
82  return ODS_STATUS_XML_ERR;
83  }
84  /* Create an XML RelaxNGs parser context for the relax-ng document. */
85  rngpctx = xmlRelaxNGNewDocParserCtxt(rngdoc);
86  if (rngpctx == NULL) {
87  ods_log_error("[%s] unable to parse file: "
88  "xmlRelaxNGNewDocParserCtxt() failed", parser_str);
89  xmlFreeDoc(rngdoc);
90  xmlFreeDoc(doc);
91  return ODS_STATUS_XML_ERR;
92  }
93  /* Parse a schema definition resource and
94  * build an internal XML schema structure.
95  */
96  schema = xmlRelaxNGParse(rngpctx);
97  if (schema == NULL) {
98  ods_log_error("[%s] unable to parse file: xmlRelaxNGParse() failed",
99  parser_str);
100  xmlRelaxNGFreeParserCtxt(rngpctx);
101  xmlFreeDoc(rngdoc);
102  xmlFreeDoc(doc);
103  return ODS_STATUS_PARSE_ERR;
104  }
105  /* Create an XML RelaxNGs validation context. */
106  rngctx = xmlRelaxNGNewValidCtxt(schema);
107  if (rngctx == NULL) {
108  ods_log_error("[%s] unable to parse file: xmlRelaxNGNewValidCtxt() "
109  "failed", parser_str);
110  xmlRelaxNGFree(schema);
111  xmlRelaxNGFreeParserCtxt(rngpctx);
112  xmlFreeDoc(rngdoc);
113  xmlFreeDoc(doc);
114  return ODS_STATUS_RNG_ERR;
115  }
116  /* Validate a document tree in memory. */
117  status = xmlRelaxNGValidateDoc(rngctx,doc);
118  if (status != 0) {
119  ods_log_error("[%s] unable to parse file: xmlRelaxNGValidateDoc() "
120  "failed", parser_str);
121  xmlRelaxNGFreeValidCtxt(rngctx);
122  xmlRelaxNGFree(schema);
123  xmlRelaxNGFreeParserCtxt(rngpctx);
124  xmlFreeDoc(rngdoc);
125  xmlFreeDoc(doc);
126  return ODS_STATUS_RNG_ERR;
127  }
128  xmlRelaxNGFreeValidCtxt(rngctx);
129  xmlRelaxNGFree(schema);
130  xmlRelaxNGFreeParserCtxt(rngpctx);
131  xmlFreeDoc(rngdoc);
132  xmlFreeDoc(doc);
133  return ODS_STATUS_OK;
134 }
135 
136 /* TODO: look how the enforcer reads this now */
137 
142 hsm_repository_t*
143 parse_conf_repositories(const char* cfgfile)
144 {
145  xmlDocPtr doc = NULL;
146  xmlXPathContextPtr xpathCtx = NULL;
147  xmlXPathObjectPtr xpathObj = NULL;
148  xmlNode* curNode = NULL;
149  xmlChar* xexpr = NULL;
150 
151  int i;
152  char* name;
153  char* module;
154  char* tokenlabel;
155  char* pin;
156  uint8_t use_pubkey;
157  uint8_t allowextract;
158  int require_backup;
159  hsm_repository_t* rlist = NULL;
160  hsm_repository_t* repo = NULL;
161 
162  /* Load XML document */
163  doc = xmlParseFile(cfgfile);
164  if (doc == NULL) {
165  ods_log_error("[%s] could not parse <RepositoryList>: "
166  "xmlParseFile() failed", parser_str);
167  return NULL;
168  }
169  /* Create xpath evaluation context */
170  xpathCtx = xmlXPathNewContext(doc);
171  if(xpathCtx == NULL) {
172  xmlFreeDoc(doc);
173  ods_log_error("[%s] could not parse <RepositoryList>: "
174  "xmlXPathNewContext() failed", parser_str);
175  return NULL;
176  }
177  /* Evaluate xpath expression */
178  xexpr = (xmlChar*) "//Configuration/RepositoryList/Repository";
179  xpathObj = xmlXPathEvalExpression(xexpr, xpathCtx);
180  if(xpathObj == NULL) {
181  xmlXPathFreeContext(xpathCtx);
182  xmlFreeDoc(doc);
183  ods_log_error("[%s] could not parse <RepositoryList>: "
184  "xmlXPathEvalExpression failed", parser_str);
185  return NULL;
186  }
187  /* Parse repositories */
188  if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
189  for (i = 0; i < xpathObj->nodesetval->nodeNr; i++) {
190  repo = NULL;
191  name = NULL;
192  module = NULL;
193  tokenlabel = NULL;
194  pin = NULL;
195  use_pubkey = 1;
196  allowextract = 0;
197  require_backup = 0;
198 
199  curNode = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
200  name = (char *) xmlGetProp(xpathObj->nodesetval->nodeTab[i],
201  (const xmlChar *)"name");
202  while (curNode) {
203  if (xmlStrEqual(curNode->name, (const xmlChar *)"RequireBackup"))
204  require_backup = 1;
205  if (xmlStrEqual(curNode->name, (const xmlChar *)"Module"))
206  module = (char *) xmlNodeGetContent(curNode);
207  if (xmlStrEqual(curNode->name, (const xmlChar *)"TokenLabel"))
208  tokenlabel = (char *) xmlNodeGetContent(curNode);
209  if (xmlStrEqual(curNode->name, (const xmlChar *)"PIN"))
210  pin = (char *) xmlNodeGetContent(curNode);
211  if (xmlStrEqual(curNode->name, (const xmlChar *)"SkipPublicKey"))
212  use_pubkey = 0;
213  if (xmlStrEqual(curNode->name, (const xmlChar *)"AllowExtraction"))
214  allowextract = 1;
215 
216  curNode = curNode->next;
217  }
218  if (name && module && tokenlabel) {
219  repo = hsm_repository_new(name, module, tokenlabel, pin,
220  use_pubkey, allowextract, require_backup);
221  }
222  if (!repo) {
223  ods_log_error("[%s] unable to add %s repository: "
224  "hsm_repository_new() failed", parser_str, name?name:"-");
225  } else {
226  repo->next = rlist;
227  rlist = repo;
228  ods_log_debug("[%s] added %s repository to repositorylist",
229  parser_str, name);
230  }
231  free((void*)name);
232  free((void*)module);
233  free((void*)tokenlabel);
234  free((void*)pin);
235  }
236  }
237 
238  xmlXPathFreeObject(xpathObj);
239  xmlXPathFreeContext(xpathCtx);
240  if (doc) {
241  xmlFreeDoc(doc);
242  }
243  return rlist;
244 }
245 
246 
252 parse_conf_listener(const char* cfgfile)
253 {
254  listener_type* listener = NULL;
255  interface_type* interface = NULL;
256  int i = 0;
257  char* address = NULL;
258  const char* port = NULL;
259  xmlDocPtr doc = NULL;
260  xmlXPathContextPtr xpathCtx = NULL;
261  xmlXPathObjectPtr xpathObj = NULL;
262  xmlNode* curNode = NULL;
263  xmlChar* xexpr = NULL;
264 
265  ods_log_assert(cfgfile);
266 
267  /* Load XML document */
268  doc = xmlParseFile(cfgfile);
269  if (doc == NULL) {
270  ods_log_error("[%s] could not parse <Listener>: "
271  "xmlParseFile() failed", parser_str);
272  return NULL;
273  }
274  /* Create xpath evaluation context */
275  xpathCtx = xmlXPathNewContext(doc);
276  if(xpathCtx == NULL) {
277  xmlFreeDoc(doc);
278  ods_log_error("[%s] could not parse <Listener>: "
279  "xmlXPathNewContext() failed", parser_str);
280  return NULL;
281  }
282  /* Evaluate xpath expression */
283  xexpr = (xmlChar*) "//Configuration/Signer/Listener/Interface";
284  xpathObj = xmlXPathEvalExpression(xexpr, xpathCtx);
285  if(xpathObj == NULL) {
286  xmlXPathFreeContext(xpathCtx);
287  xmlFreeDoc(doc);
288  ods_log_error("[%s] could not parse <Listener>: "
289  "xmlXPathEvalExpression failed", parser_str);
290  return NULL;
291  }
292  /* Parse interfaces */
293  listener = listener_create();
294  ods_log_assert(listener);
295 
296  /* If port is not set in Listener in the conf file, default value is used.
297  * default port: 15354
298  */
299  if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
300  for (i = 0; i < xpathObj->nodesetval->nodeNr; i++) {
301  address = NULL;
302  port = strdup("15354");
303 
304  curNode = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
305  while (curNode) {
306  if (xmlStrEqual(curNode->name, (const xmlChar *)"Address")) {
307  address = (char *) xmlNodeGetContent(curNode);
308  } else if (xmlStrEqual(curNode->name, (const xmlChar *)"Port")) {
309  free((char *)port);
310  port = (char *) xmlNodeGetContent(curNode);
311  }
312  curNode = curNode->next;
313  }
314  if (address) {
315  interface = listener_push(listener, address,
316  acl_parse_family(address), port);
317  } else {
318  interface = listener_push(listener, (char *)"", AF_INET, port);
319  if (interface) {
320  interface = listener_push(listener, (char *)"", AF_INET6, port);
321  }
322  }
323  if (!interface) {
324  ods_log_error("[%s] unable to add %s:%s interface: "
325  "listener_push() failed", parser_str, address?address:"",
326  port);
327  } else {
328  ods_log_debug("[%s] added %s:%s interface to listener",
329  parser_str, address?address:"", port);
330  }
331  free((void*)port);
332  free((void*)address);
333  }
334  }
335  else {
336  interface = listener_push(listener, (char *)"", AF_INET, "15354");
337  if (interface) {
338  interface = listener_push(listener, (char *)"", AF_INET6, "15354");
339  }
340  }
341  xmlXPathFreeObject(xpathObj);
342  xmlXPathFreeContext(xpathCtx);
343  if (doc) {
344  xmlFreeDoc(doc);
345  }
346  return listener;
347 }
348 
349 
354 const char*
355 parse_conf_string(const char* cfgfile, const char* expr, int required)
356 {
357  xmlDocPtr doc = NULL;
358  xmlXPathContextPtr xpathCtx = NULL;
359  xmlXPathObjectPtr xpathObj = NULL;
360  xmlChar *xexpr = NULL;
361  const char* string = NULL;
362 
363  ods_log_assert(expr);
364  ods_log_assert(cfgfile);
365 
366  /* Load XML document */
367  doc = xmlParseFile(cfgfile);
368  if (doc == NULL) {
369  ods_log_error("[%s] unable to parse file %s: xmlParseFile() failed",
370  parser_str, cfgfile);
371  return NULL;
372  }
373  /* Create xpath evaluation context */
374  xpathCtx = xmlXPathNewContext(doc);
375  if (xpathCtx == NULL) {
376  ods_log_error("[%s] unable to parse file %s: xmlXPathNewContext() "
377  "failed", parser_str, cfgfile);
378  xmlFreeDoc(doc);
379  return NULL;
380  }
381  /* Get string */
382  xexpr = (unsigned char*) expr;
383  xpathObj = xmlXPathEvalExpression(xexpr, xpathCtx);
384  if (xpathObj == NULL || xpathObj->nodesetval == NULL ||
385  xpathObj->nodesetval->nodeNr <= 0) {
386  if (required) {
387  ods_log_error("[%s] unable to evaluate expression %s in cfgile %s",
388  parser_str, (char*) xexpr, cfgfile);
389  }
390  xmlXPathFreeContext(xpathCtx);
391  if (xpathObj) {
392  xmlXPathFreeObject(xpathObj);
393  }
394  xmlFreeDoc(doc);
395  return NULL;
396  }
397  if (xpathObj->nodesetval != NULL &&
398  xpathObj->nodesetval->nodeNr > 0) {
399  string = (const char*) xmlXPathCastToString(xpathObj);
400  xmlXPathFreeContext(xpathCtx);
401  xmlXPathFreeObject(xpathObj);
402  xmlFreeDoc(doc);
403  return string;
404  }
405  xmlXPathFreeContext(xpathCtx);
406  xmlXPathFreeObject(xpathObj);
407  xmlFreeDoc(doc);
408  return NULL;
409 }
410 
411 /*
412  * TODO all parse routines parse the complete file. Yuk!
413  * TODO make a parse_conf_bool for testing existence of empty elements
414  * instead of abusing parse_conf_string
415  * */
416 
417 const char*
418 parse_conf_zonelist_filename(const char* cfgfile)
419 {
420  int lwd = 0;
421  int lzl = 0;
422  int found = 0;
423  char* dup = NULL;
424  const char* str = parse_conf_string(
425  cfgfile,
426  "//Configuration/Enforcer/WorkingDirectory",
427  0);
428 
429  if (str) {
430  found = 1;
431  } else {
432  str = OPENDNSSEC_ENFORCER_WORKINGDIR;
433  }
434  lwd = strlen(str);
435  lzl = strlen(OPENDNSSEC_ENFORCER_ZONELIST);
436  if (lwd>0 && strncmp(str + (lwd-1), "/", 1) != 0) {
437  CHECKALLOC(dup = malloc(sizeof(char)*(lwd+lzl+2)));
438  memcpy(dup, str, sizeof(char)*(lwd+1));
439  strlcat(dup, "/", sizeof(char)*(lwd+2));
440  strlcat(dup, OPENDNSSEC_ENFORCER_ZONELIST, sizeof(char)*(lwd+lzl+2));
441  lwd += (lzl+1);
442  } else {
443  CHECKALLOC(dup = malloc(sizeof(char)*(lwd+lzl+1)));
444  memcpy(dup, str, sizeof(char)*(lwd+1));
445  strlcat(dup, OPENDNSSEC_ENFORCER_ZONELIST, sizeof(char)*(lwd+lzl+1));
446  lwd += (lzl+1);
447  }
448  if (found) {
449  free((void*)str);
450  }
451  ods_log_assert(dup);
452  return (const char*) dup;
453 }
454 
455 
456 const char*
457 parse_conf_log_filename(const char* cfgfile)
458 {
459  const char* dup = NULL;
460  const char* str = parse_conf_string(cfgfile,
461  "//Configuration/Common/Logging/Syslog/Facility",
462  0);
463  if (!str) {
464  str = parse_conf_string(cfgfile,
465  "//Configuration/Common/Logging/File/Filename",
466  0);
467  }
468  if (str) {
469  dup = strdup(str);
470  free((void*)str);
471  }
472  return dup; /* NULL, Facility or Filename */
473 }
474 
475 
476 const char*
477 parse_conf_pid_filename(const char* cfgfile)
478 {
479  const char* dup = NULL;
480  const char* str = parse_conf_string(
481  cfgfile,
482  "//Configuration/Signer/PidFile",
483  0);
484 
485  if (str) {
486  dup = strdup(str);
487  free((void*)str);
488  } else {
489  dup = strdup(ODS_SE_PIDFILE);
490  }
491  return dup;
492 }
493 
494 
495 const char*
496 parse_conf_notify_command(const char* cfgfile)
497 {
498  const char* dup = NULL;
499  const char* str = parse_conf_string(
500  cfgfile,
501  "//Configuration/Signer/NotifyCommand",
502  0);
503 
504  if (str) {
505  dup = strdup(str);
506  free((void*)str);
507  }
508  return dup;
509 }
510 
511 
512 const char*
513 parse_conf_clisock_filename(const char* cfgfile)
514 {
515  char* dup = NULL;
516  const char* str = parse_conf_string(
517  cfgfile,
518  "//Configuration/Signer/SocketFile",
519  0);
520 
521  if (str) {
522  dup = strdup(str);
523  free((void*)str);
524  } else {
525  dup = strdup(ODS_SE_SOCKFILE);
526  }
527  if (strlen(dup) >= sizeof(((struct sockaddr_un*)0)->sun_path)) {
528  dup[sizeof(((struct sockaddr_un*)0)->sun_path)-1] = '\0'; /* don't worry about just a few bytes 'lost' */
529  ods_log_warning("[%s] SocketFile path too long, truncated to %s", parser_str, dup);
530  }
531  return dup;
532 }
533 
534 
535 const char*
536 parse_conf_working_dir(const char* cfgfile)
537 {
538  const char* dup = NULL;
539  const char* str = parse_conf_string(
540  cfgfile,
541  "//Configuration/Signer/WorkingDirectory",
542  0);
543 
544  if (str) {
545  dup = strdup(str);
546  free((void*)str);
547  } else {
548  dup = strdup(ODS_SE_WORKDIR);
549  }
550  ods_log_assert(dup);
551  return dup;
552 }
553 
554 
555 const char*
556 parse_conf_username(const char* cfgfile)
557 {
558  const char* dup = NULL;
559  const char* str = parse_conf_string(
560  cfgfile,
561  "//Configuration/Signer/Privileges/User",
562  0);
563 
564  if (str) {
565  dup = strdup(str);
566  free((void*)str);
567  }
568  return dup;
569 }
570 
571 
572 const char*
573 parse_conf_group(const char* cfgfile)
574 {
575  const char* dup = NULL;
576  const char* str = parse_conf_string(
577  cfgfile,
578  "//Configuration/Signer/Privileges/Group",
579  0);
580 
581  if (str) {
582  dup = strdup(str);
583  free((void*)str);
584  }
585  return dup;
586 }
587 
588 
589 const char*
590 parse_conf_chroot(const char* cfgfile)
591 {
592  const char* dup = NULL;
593  const char* str = parse_conf_string(
594  cfgfile,
595  "//Configuration/Signer/Privileges/Directory",
596  0);
597 
598  if (str) {
599  dup = strdup(str);
600  free((void*)str);
601  }
602  return dup;
603 }
604 
605 
610 int
611 parse_conf_use_syslog(const char* cfgfile)
612 {
613  const char* str = parse_conf_string(cfgfile,
614  "//Configuration/Common/Logging/Syslog/Facility",
615  0);
616  if (str) {
617  free((void*)str);
618  return 1;
619  }
620  return 0;
621 }
622 
623 int
624 parse_conf_verbosity(const char* cfgfile)
625 {
626  int verbosity = ODS_SE_VERBOSITY;
627  const char* str = parse_conf_string(cfgfile,
628  "//Configuration/Common/Logging/Verbosity",
629  0);
630  if (str) {
631  if (strlen(str) > 0) {
632  verbosity = atoi(str);
633  }
634  free((void*)str);
635  }
636  return verbosity;
637 }
638 
639 
640 int
641 parse_conf_worker_threads(const char* cfgfile)
642 {
643  int numwt = ODS_SE_WORKERTHREADS;
644  const char* str = parse_conf_string(cfgfile,
645  "//Configuration/Signer/WorkerThreads",
646  0);
647  if (str) {
648  if (strlen(str) > 0) {
649  numwt = atoi(str);
650  }
651  free((void*)str);
652  }
653  return numwt;
654 }
655 
656 
657 int
658 parse_conf_signer_threads(const char* cfgfile)
659 {
660  int numwt = ODS_SE_WORKERTHREADS;
661  const char* str = parse_conf_string(cfgfile,
662  "//Configuration/Signer/SignerThreads",
663  0);
664  if (str) {
665  if (strlen(str) > 0) {
666  numwt = atoi(str);
667  }
668  free((void*)str);
669  return numwt;
670  }
671  /* no SignerThreads value configured, look at WorkerThreads */
672  return parse_conf_worker_threads(cfgfile);
673 }
int acl_parse_family(const char *a)
Definition: acl.c:104
const char * parse_conf_pid_filename(const char *cfgfile)
Definition: confparser.c:477
const char * parse_conf_log_filename(const char *cfgfile)
Definition: confparser.c:457
const char * parse_conf_notify_command(const char *cfgfile)
Definition: confparser.c:496
const char * parse_conf_chroot(const char *cfgfile)
Definition: confparser.c:590
const char * parse_conf_working_dir(const char *cfgfile)
Definition: confparser.c:536
const char * parse_conf_string(const char *cfgfile, const char *expr, int required)
Definition: confparser.c:355
int parse_conf_worker_threads(const char *cfgfile)
Definition: confparser.c:641
int parse_conf_signer_threads(const char *cfgfile)
Definition: confparser.c:658
int parse_conf_use_syslog(const char *cfgfile)
Definition: confparser.c:611
const char * parse_conf_clisock_filename(const char *cfgfile)
Definition: confparser.c:513
const char * parse_conf_zonelist_filename(const char *cfgfile)
Definition: confparser.c:418
listener_type * parse_conf_listener(const char *cfgfile)
Definition: confparser.c:252
const char * parse_conf_group(const char *cfgfile)
Definition: confparser.c:573
hsm_repository_t * parse_conf_repositories(const char *cfgfile)
Definition: confparser.c:143
const char * parse_conf_username(const char *cfgfile)
Definition: confparser.c:556
int parse_conf_verbosity(const char *cfgfile)
Definition: confparser.c:624
ods_status parse_file_check(const char *cfgfile, const char *rngfile)
Definition: confparser.c:55
interface_type * listener_push(listener_type *listener, char *address, int family, const char *port)
Definition: listener.c:60
listener_type * listener_create()
Definition: listener.c:45