updates.
[silc.git] / apps / silc / clientconfig.c
1 /*
2
3   serverconfig.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1997 - 2000 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "clientincludes.h"
23 #include "clientconfig.h"
24
25 /* 
26    All possible configuration sections for SILC client.
27 */
28 SilcClientConfigSection silc_client_config_sections[] = {
29   { "[cipher]", 
30     SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, 4 },
31   { "[pkcs]", 
32     SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, 2 },
33   { "[hash]", 
34     SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, 4 },
35   { "[connection]", 
36     SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, 4 },
37   { "[commands]", 
38     SILC_CLIENT_CONFIG_SECTION_TYPE_COMMAND, 0 },
39   
40   { NULL, SILC_CLIENT_CONFIG_SECTION_TYPE_NONE, 0 }
41 };
42
43 /* Allocates a new configuration object, opens configuration file and
44    parses the file. The parsed data is returned to the newly allocated
45    configuration object. */
46
47 SilcClientConfig silc_client_config_alloc(char *filename)
48 {
49   SilcClientConfig new;
50   SilcBuffer buffer;
51   SilcClientConfigParse config_parse;
52
53   SILC_LOG_DEBUG(("Allocating new configuration object"));
54
55   new = silc_calloc(1, sizeof(*new));
56   new->filename = filename;
57
58   /* Open configuration file and parse it */
59   config_parse = NULL;
60   buffer = NULL;
61   silc_config_open(filename, &buffer);
62   if (!buffer)
63     goto fail;
64   if ((silc_client_config_parse(new, buffer, &config_parse)) == FALSE)
65     goto fail;
66   if ((silc_client_config_parse_lines(new, config_parse)) == FALSE)
67     goto fail;
68
69   silc_free(buffer);
70
71   return new;
72
73  fail:
74   silc_free(new);
75   return NULL;
76 }
77
78 /* Free's a configuration object. */
79
80 void silc_client_config_free(SilcClientConfig config)
81 {
82   if (config) {
83
84     silc_free(config);
85   }
86 }
87
88 /* Parses the the buffer and returns the parsed lines into return_config
89    argument. The return_config argument doesn't have to be initialized 
90    before calling this. It will be initialized during the parsing. The
91    buffer sent as argument can be safely free'd after this function has
92    succesfully returned. */
93
94 int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, 
95                              SilcClientConfigParse *return_config)
96 {
97   int i, begin;
98   unsigned int linenum;
99   char line[1024], *cp;
100   SilcClientConfigSection *cptr = NULL;
101   SilcClientConfigParse parse = *return_config, first = NULL;
102
103   SILC_LOG_DEBUG(("Parsing configuration file"));
104
105   begin = 0;
106   linenum = 0;
107   while((begin = silc_gets(line, sizeof(line), 
108                            buffer->data, buffer->len, begin)) != EOF) {
109     cp = line;
110     linenum++;
111
112     /* Check for bad line */
113     if (silc_check_line(cp))
114       continue;
115
116     /* Remove tabs and whitespaces from the line */
117     if (strchr(cp, '\t')) {
118       i = 0;
119       while(strchr(cp + i, '\t')) {
120         *strchr(cp + i, '\t') = ' ';
121         i++;
122       }
123     }
124     for (i = 0; i < strlen(cp); i++) {
125       if (cp[i] != ' ') {
126         if (i)
127           cp++;
128         break;
129       }
130       cp++;
131     }
132
133     /* Parse line */
134     switch(cp[0]) {
135     case '[':
136       /*
137        * Start of a section
138        */
139
140       /* Remove new line sign */
141       if (strchr(cp, '\n'))
142         *strchr(cp, '\n') = '\0';
143       
144       /* Check for matching sections */
145       for (cptr = silc_client_config_sections; cptr->section; cptr++)
146         if (!strncasecmp(cp, cptr->section, strlen(cptr->section)))
147           break;
148
149       if (!cptr->section) {
150         fprintf(stderr, "%s:%d: Unknown section `%s'\n", 
151                         config->filename, linenum, cp);
152         return FALSE;
153       }
154
155       break;
156     default:
157       /*
158        * Start of a configuration line
159        */
160
161       /* Handle config section */
162       if (cptr->type != SILC_CLIENT_CONFIG_SECTION_TYPE_NONE) {
163         
164         if (strchr(cp, '\n'))
165             *strchr(cp, '\n') = ':';
166
167         if (parse == NULL) {
168           parse = silc_calloc(1, sizeof(*parse));
169           parse->line = NULL;
170           parse->section = NULL;
171           parse->next = NULL;
172           parse->prev = NULL;
173         } else {
174           if (parse->next == NULL) {
175             parse->next = silc_calloc(1, sizeof(*parse->next));
176             parse->next->line = NULL;
177             parse->next->section = NULL;
178             parse->next->next = NULL;
179             parse->next->prev = parse;
180             parse = parse->next;
181           }
182         }
183         
184         if (first == NULL)
185           first = parse;
186
187         /* Add the line to parsing structure for further parsing. */
188         if (parse) {
189           parse->section = cptr;
190           parse->line = silc_buffer_alloc(strlen(cp) + 1);
191           parse->linenum = linenum;
192           silc_buffer_pull_tail(parse->line, strlen(cp));
193           silc_buffer_put(parse->line, cp, strlen(cp));
194         }
195       }
196       break;
197     }
198   }
199   
200   /* Set the return_config argument to its first value so that further
201      parsing can be started from the first line. */
202   *return_config = first;
203
204   return TRUE;
205 }
206
207 /* Parses the lines earlier read from configuration file. The config object
208    must not be initialized, it will be initialized in this function. The
209    parse_config argument is uninitialized automatically during this
210    function. */
211
212 int silc_client_config_parse_lines(SilcClientConfig config, 
213                                    SilcClientConfigParse parse_config)
214 {
215   int ret, check = FALSE;
216   char *tmp;
217   SilcClientConfigParse pc = parse_config;
218   SilcBuffer line;
219
220   SILC_LOG_DEBUG(("Parsing configuration lines"));
221   
222   if (!config)
223     return FALSE;
224   
225   while(pc) {
226     check = FALSE;
227     line = pc->line;
228
229     /* Get number of tokens in line (command section is handeled
230        specially and has no tokens at all). */
231     ret = silc_config_check_num_token(line);
232     if (ret != pc->section->maxfields && 
233         pc->section->type != SILC_CLIENT_CONFIG_SECTION_TYPE_COMMAND) {
234       /* Bad line */
235       fprintf(stderr, "%s:%d: Missing tokens, %d tokens (should be %d)\n",
236               config->filename, pc->linenum, ret, 
237               pc->section->maxfields);
238       break;
239     }
240
241     /* Parse the line */
242     switch(pc->section->type) {
243     case SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER:
244
245       if (!config->cipher) {
246         config->cipher = silc_calloc(1, sizeof(*config->cipher));
247         config->cipher->next = NULL;
248         config->cipher->prev = NULL;
249       } else {
250         if (!config->cipher->next) {
251           config->cipher->next = 
252             silc_calloc(1, sizeof(*config->cipher->next));
253           config->cipher->next->next = NULL;
254           config->cipher->next->prev = config->cipher;
255           config->cipher = config->cipher->next;
256         }
257       }
258
259       /* Get cipher name */
260       ret = silc_config_get_token(line, &config->cipher->alg_name);
261       if (ret < 0)
262         break;
263       if (ret == 0) {
264         fprintf(stderr, "%s:%d: Cipher name not defined\n",
265                 config->filename, pc->linenum);
266         break;
267       }
268
269       /* Get module name */
270       config->cipher->sim_name = NULL;
271       ret = silc_config_get_token(line, &config->cipher->sim_name);
272       if (ret < 0)
273         break;
274
275       /* Get key length */
276       ret = silc_config_get_token(line, &tmp);
277       if (ret < 0)
278         break;
279       if (ret == 0) {
280         fprintf(stderr, "%s:%d: Cipher key length not defined\n",
281                 config->filename, pc->linenum);
282         break;
283       }
284       config->cipher->key_len = atoi(tmp);
285       silc_free(tmp);
286
287       /* Get block length */
288       ret = silc_config_get_token(line, &tmp);
289       if (ret < 0)
290         break;
291       if (ret == 0) {
292         fprintf(stderr, "%s:%d: Cipher block length not defined\n",
293                 config->filename, pc->linenum);
294         break;
295       }
296       config->cipher->block_len = atoi(tmp);
297       silc_free(tmp);
298
299       check = TRUE;
300       break;
301
302     case SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS:
303
304       if (!config->pkcs) {
305         config->pkcs = silc_calloc(1, sizeof(*config->pkcs));
306         config->pkcs->next = NULL;
307         config->pkcs->prev = NULL;
308       } else {
309         if (!config->pkcs->next) {
310           config->pkcs->next = 
311             silc_calloc(1, sizeof(*config->pkcs->next));
312           config->pkcs->next->next = NULL;
313           config->pkcs->next->prev = config->pkcs;
314           config->pkcs = config->pkcs->next;
315         }
316       }
317
318       /* Get PKCS name */
319       ret = silc_config_get_token(line, &config->pkcs->alg_name);
320       if (ret < 0)
321         break;
322       if (ret == 0) {
323         fprintf(stderr, "%s:%d: PKCS name not defined\n",
324                 config->filename, pc->linenum);
325         break;
326       }
327
328       /* Get key length */
329       ret = silc_config_get_token(line, &tmp);
330       if (ret < 0)
331         break;
332       if (ret == 0) {
333         fprintf(stderr, "%s:%d: PKCS key length not defined\n",
334                 config->filename, pc->linenum);
335         break;
336       }
337       config->pkcs->key_len = atoi(tmp);
338       silc_free(tmp);
339
340       check = TRUE;
341       break;
342
343     case SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION:
344
345       if (!config->hash_func) {
346         config->hash_func = silc_calloc(1, sizeof(*config->hash_func));
347         config->hash_func->next = NULL;
348         config->hash_func->prev = NULL;
349       } else {
350         if (!config->hash_func->next) {
351           config->hash_func->next = 
352             silc_calloc(1, sizeof(*config->hash_func->next));
353           config->hash_func->next->next = NULL;
354           config->hash_func->next->prev = config->hash_func;
355           config->hash_func = config->hash_func->next;
356         }
357       }
358
359       /* Get Hash function name */
360       ret = silc_config_get_token(line, &config->hash_func->alg_name);
361       if (ret < 0)
362         break;
363       if (ret == 0) {
364         fprintf(stderr, "%s:%d: Hash function name not defined\n",
365                 config->filename, pc->linenum);
366         break;
367       }
368       
369       /* Get Hash function module name */
370       config->hash_func->sim_name = NULL;
371       ret = silc_config_get_token(line, &config->hash_func->sim_name);
372       if (ret < 0)
373         break;
374
375       /* Get block length */
376       ret = silc_config_get_token(line, &tmp);
377       if (ret < 0)
378         break;
379       if (ret == 0) {
380         fprintf(stderr, "%s:%d: Hash function block length not defined\n",
381                 config->filename, pc->linenum);
382         break;
383       }
384       config->hash_func->block_len = atoi(tmp);
385       silc_free(tmp);
386
387       /* Get hash length */
388       ret = silc_config_get_token(line, &tmp);
389       if (ret < 0)
390         break;
391       if (ret == 0) {
392         fprintf(stderr, "%s:%d: Hash function hash length not defined\n",
393                 config->filename, pc->linenum);
394         break;
395       }
396       config->hash_func->key_len = atoi(tmp);
397       silc_free(tmp);
398
399       check = TRUE;
400       break;
401
402     case SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION:
403
404       if (!config->conns) {
405         config->conns = silc_calloc(1, sizeof(*config->conns));
406         config->conns->next = NULL;
407         config->conns->prev = NULL;
408       } else {
409         if (!config->conns->next) {
410           config->conns->next = silc_calloc(1, sizeof(*config->conns));
411           config->conns->next->next = NULL;
412           config->conns->next->prev = config->conns;
413           config->conns = config->conns->next;
414         }
415       }
416       
417       /* Get host */
418       ret = silc_config_get_token(line, &config->conns->host);
419       if (ret < 0)
420         break;
421       if (ret == 0)
422         /* Any host */
423         config->conns->host = strdup("*");
424
425       /* Get authentication method */
426       ret = silc_config_get_token(line, &tmp);
427       if (ret < 0)
428         break;
429       if (ret) {
430         if (strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD) &&
431             strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY)) {
432           fprintf(stderr, "%s:%d: Unknown authentication method '%s'\n",
433                   config->filename, pc->linenum, tmp);
434           break;
435         }
436
437         if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD))
438           config->conns->auth_meth = SILC_AUTH_PASSWORD;
439
440         if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY))
441           config->conns->auth_meth = SILC_AUTH_PUBLIC_KEY;
442
443         silc_free(tmp);
444       }
445
446       /* Get authentication data */
447       ret = silc_config_get_token(line, &config->conns->auth_data);
448       if (ret < 0)
449         break;
450
451       /* Get port */
452       ret = silc_config_get_token(line, &tmp);
453       if (ret < 0)
454         break;
455       if (ret) {
456         config->conns->port = atoi(tmp);
457         silc_free(tmp);
458       }
459
460       check = TRUE;
461       break;
462
463     case SILC_CLIENT_CONFIG_SECTION_TYPE_COMMAND:
464
465       if (!config->commands) {
466         config->commands = silc_calloc(1, sizeof(*config->commands));
467         config->commands->next = NULL;
468         config->commands->prev = NULL;
469       } else {
470         if (!config->commands->next) {
471           config->commands->next = silc_calloc(1, sizeof(*config->commands));
472           config->commands->next->next = NULL;
473           config->commands->next->prev = config->commands;
474           config->commands = config->commands->next;
475         }
476       }
477       
478       /* Get command line (this may include parameters as well. They
479          will be parsed later with standard command parser when
480          executing particular command.) */
481       config->commands->command = silc_calloc(strlen(line->data), 
482                                               sizeof(char));
483       memcpy(config->commands->command, line->data, strlen(line->data) - 1);
484       if (ret < 0)
485         break;
486
487       check = TRUE;
488       break;
489
490     case SILC_CLIENT_CONFIG_SECTION_TYPE_NONE:
491     default:
492       break;
493     }
494
495     /* Check for error */
496     if (check == FALSE) {
497       /* Line could not be parsed */
498       fprintf(stderr, "%s:%d: Parse error\n", config->filename, pc->linenum);
499       break;
500     }
501
502     pc = pc->next;
503   }
504
505   if (check == FALSE)
506     return FALSE;;
507
508   /* Before returning all the lists in the config object must be set
509      to their first values (the last value is first here). */
510   while (config->cipher && config->cipher->prev)
511     config->cipher = config->cipher->prev;
512   while (config->pkcs && config->pkcs->prev)
513     config->pkcs = config->pkcs->prev;
514   while (config->hash_func && config->hash_func->prev)
515     config->hash_func = config->hash_func->prev;
516   while (config->conns && config->conns->prev)
517     config->conns = config->conns->prev;
518   while (config->commands && config->commands->prev)
519     config->commands = config->commands->prev;
520   
521   SILC_LOG_DEBUG(("Done"));
522   
523   return TRUE;
524 }
525
526 /* Registers configured ciphers. These can then be allocated by the
527    client when needed. */
528
529 void silc_client_config_register_ciphers(SilcClientConfig config)
530 {
531   SilcClientConfigSectionAlg *alg;
532   SilcClientInternal app = (SilcClientInternal)config->client;
533   SilcClient client = app->client;
534
535   SILC_LOG_DEBUG(("Registering configured ciphers"));
536
537   alg = config->cipher;
538   while(alg) {
539
540     if (!alg->sim_name) {
541       /* Crypto module is supposed to be built in. Nothing to be done
542          here except to test that the cipher really is built in. */
543       SilcCipher tmp = NULL;
544
545       if (silc_cipher_alloc(alg->alg_name, &tmp) == FALSE) {
546         SILC_LOG_ERROR(("Unsupported cipher `%s'", alg->alg_name));
547         silc_client_stop(client);
548         exit(1);
549       }
550       silc_cipher_free(tmp);
551
552 #ifdef SILC_SIM
553     } else {
554       /* Load (try at least) the crypto SIM module */
555       SilcCipherObject cipher;
556       SilcSimContext *sim;
557       char *alg_name;
558
559       memset(&cipher, 0, sizeof(cipher));
560       cipher.name = alg->alg_name;
561       cipher.block_len = alg->block_len;
562       cipher.key_len = alg->key_len * 8;
563
564       sim = silc_sim_alloc();
565       sim->type = SILC_SIM_CIPHER;
566       sim->libname = alg->sim_name;
567
568       alg_name = strdup(alg->alg_name);
569       if (strchr(alg_name, '-'))
570         *strchr(alg_name, '-') = '\0';
571
572       if ((silc_sim_load(sim))) {
573         cipher.set_key = 
574           silc_sim_getsym(sim, silc_sim_symname(alg_name, 
575                                                 SILC_CIPHER_SIM_SET_KEY));
576         SILC_LOG_DEBUG(("set_key=%p", cipher.set_key));
577         cipher.set_key_with_string = 
578           silc_sim_getsym(sim, silc_sim_symname(alg_name, 
579                                                 SILC_CIPHER_SIM_SET_KEY_WITH_STRING));
580         SILC_LOG_DEBUG(("set_key_with_string=%p", cipher.set_key_with_string));
581         cipher.encrypt = 
582           silc_sim_getsym(sim, silc_sim_symname(alg_name,
583                                                 SILC_CIPHER_SIM_ENCRYPT_CBC));
584         SILC_LOG_DEBUG(("encrypt_cbc=%p", cipher.encrypt));
585         cipher.decrypt = 
586           silc_sim_getsym(sim, silc_sim_symname(alg_name,
587                                                 SILC_CIPHER_SIM_DECRYPT_CBC));
588         SILC_LOG_DEBUG(("decrypt_cbc=%p", cipher.decrypt));
589         cipher.context_len = 
590           silc_sim_getsym(sim, silc_sim_symname(alg_name,
591                                                 SILC_CIPHER_SIM_CONTEXT_LEN));
592         SILC_LOG_DEBUG(("context_len=%p", cipher.context_len));
593
594         /* Put the SIM to the table of all SIM's in client */
595         app->sim = silc_realloc(app->sim,
596                                    sizeof(*app->sim) * 
597                                    (app->sim_count + 1));
598         app->sim[app->sim_count] = sim;
599         app->sim_count++;
600
601         silc_free(alg_name);
602       } else {
603         SILC_LOG_ERROR(("Error configuring ciphers"));
604         silc_client_stop(client);
605         exit(1);
606       }
607
608       /* Register the cipher */
609       silc_cipher_register(&cipher);
610 #endif
611     }
612
613     alg = alg->next;
614   }
615 }
616
617 /* Registers configured PKCS's. */
618 /* XXX: This really doesn't do anything now since we have statically
619    registered our PKCS's. This should be implemented when PKCS works
620    as SIM's. This checks now only that the PKCS user requested is 
621    really out there. */
622
623 void silc_client_config_register_pkcs(SilcClientConfig config)
624 {
625   SilcClientConfigSectionAlg *alg = config->pkcs;
626   SilcClientInternal app = (SilcClientInternal)config->client;
627   SilcClient client = app->client;
628   SilcPKCS tmp = NULL;
629
630   SILC_LOG_DEBUG(("Registering configured PKCS"));
631
632   while(alg) {
633
634     if (silc_pkcs_alloc(alg->alg_name, &tmp) == FALSE) {
635       SILC_LOG_ERROR(("Unsupported PKCS `%s'", alg->alg_name));
636       silc_client_stop(client);
637       exit(1);
638     }
639     silc_free(tmp);
640
641     alg = alg->next;
642   }
643 }
644
645 /* Registers configured hash functions. These can then be allocated by the
646    client when needed. */
647
648 void silc_client_config_register_hashfuncs(SilcClientConfig config)
649 {
650   SilcClientConfigSectionAlg *alg;
651   SilcClientInternal app = (SilcClientInternal)config->client;
652   SilcClient client = app->client;
653
654   SILC_LOG_DEBUG(("Registering configured hash functions"));
655
656   alg = config->hash_func;
657   while(alg) {
658
659     if (!alg->sim_name) {
660       /* Hash module is supposed to be built in. Nothing to be done
661          here except to test that the hash function really is built in. */
662       SilcHash tmp = NULL;
663
664       if (silc_hash_alloc(alg->alg_name, &tmp) == FALSE) {
665         SILC_LOG_ERROR(("Unsupported hash function `%s'", alg->alg_name));
666         silc_client_stop(client);
667         exit(1);
668       }
669       silc_free(tmp);
670
671 #ifdef SILC_SIM
672     } else {
673       /* Load (try at least) the hash SIM module */
674       SilcHashObject hash;
675       SilcSimContext *sim;
676
677       memset(&hash, 0, sizeof(hash));
678       hash.name = alg->alg_name;
679       hash.block_len = alg->block_len;
680       hash.hash_len = alg->key_len;
681
682       sim = silc_sim_alloc();
683       sim->type = SILC_SIM_HASH;
684       sim->libname = alg->sim_name;
685
686       if ((silc_sim_load(sim))) {
687         hash.init = 
688           silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, 
689                                                 SILC_HASH_SIM_INIT));
690         SILC_LOG_DEBUG(("init=%p", hash.init));
691         hash.update = 
692           silc_sim_getsym(sim, silc_sim_symname(alg->alg_name,
693                                                 SILC_HASH_SIM_UPDATE));
694         SILC_LOG_DEBUG(("update=%p", hash.update));
695         hash.final = 
696           silc_sim_getsym(sim, silc_sim_symname(alg->alg_name,
697                                                 SILC_HASH_SIM_FINAL));
698         SILC_LOG_DEBUG(("final=%p", hash.final));
699         hash.context_len = 
700           silc_sim_getsym(sim, silc_sim_symname(alg->alg_name,
701                                                 SILC_HASH_SIM_CONTEXT_LEN));
702         SILC_LOG_DEBUG(("context_len=%p", hash.context_len));
703
704         /* Put the SIM to the table of all SIM's in client */
705         app->sim = silc_realloc(app->sim,
706                                    sizeof(*app->sim) * 
707                                    (app->sim_count + 1));
708         app->sim[app->sim_count] = sim;
709         app->sim_count++;
710       } else {
711         SILC_LOG_ERROR(("Error configuring hash functions"));
712         silc_client_stop(client);
713         exit(1);
714       }
715
716       /* Register the cipher */
717       silc_hash_register(&hash);
718 #endif
719     }
720
721     alg = alg->next;
722   }
723 }
724
725
726 SilcClientConfigSectionConnection *
727 silc_client_config_find_connection(SilcClientConfig config, 
728                                    char *host, int port)
729 {
730   int i;
731   SilcClientConfigSectionConnection *conn = NULL;
732
733   SILC_LOG_DEBUG(("Finding connection"));
734
735   if (!host)
736     return NULL;
737
738   if (!config->conns)
739     return NULL;
740
741   conn = config->conns;
742   for (i = 0; conn; i++) {
743     if (silc_string_compare(conn->host, host))
744       break;
745     conn = conn->next;
746   }
747
748   if (!conn)
749     return NULL;
750
751   SILC_LOG_DEBUG(("Found match"));
752
753   return conn;
754 }