]>
Commit | Line | Data |
---|---|---|
1c1af145 | 1 | /* |
2 | * Platform-independent routines shared between all PuTTY programs. | |
3 | */ | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <stdarg.h> | |
8 | #include <limits.h> | |
9 | #include <ctype.h> | |
10 | #include <assert.h> | |
11 | #include "putty.h" | |
12 | ||
13 | /* | |
14 | * Parse a string block size specification. This is approximately a | |
15 | * subset of the block size specs supported by GNU fileutils: | |
16 | * "nk" = n kilobytes | |
17 | * "nM" = n megabytes | |
18 | * "nG" = n gigabytes | |
19 | * All numbers are decimal, and suffixes refer to powers of two. | |
20 | * Case-insensitive. | |
21 | */ | |
22 | unsigned long parse_blocksize(const char *bs) | |
23 | { | |
24 | char *suf; | |
25 | unsigned long r = strtoul(bs, &suf, 10); | |
26 | if (*suf != '\0') { | |
27 | while (*suf && isspace((unsigned char)*suf)) suf++; | |
28 | switch (*suf) { | |
29 | case 'k': case 'K': | |
30 | r *= 1024ul; | |
31 | break; | |
32 | case 'm': case 'M': | |
33 | r *= 1024ul * 1024ul; | |
34 | break; | |
35 | case 'g': case 'G': | |
36 | r *= 1024ul * 1024ul * 1024ul; | |
37 | break; | |
38 | case '\0': | |
39 | default: | |
40 | break; | |
41 | } | |
42 | } | |
43 | return r; | |
44 | } | |
45 | ||
46 | /* | |
47 | * Parse a ^C style character specification. | |
48 | * Returns NULL in `next' if we didn't recognise it as a control character, | |
49 | * in which case `c' should be ignored. | |
50 | * The precise current parsing is an oddity inherited from the terminal | |
51 | * answerback-string parsing code. All sequences start with ^; all except | |
52 | * ^<123> are two characters. The ones that are worth keeping are probably: | |
53 | * ^? 127 | |
54 | * ^@A-Z[\]^_ 0-31 | |
55 | * a-z 1-26 | |
56 | * <num> specified by number (decimal, 0octal, 0xHEX) | |
57 | * ~ ^ escape | |
58 | */ | |
59 | char ctrlparse(char *s, char **next) | |
60 | { | |
61 | char c = 0; | |
62 | if (*s != '^') { | |
63 | *next = NULL; | |
64 | } else { | |
65 | s++; | |
66 | if (*s == '\0') { | |
67 | *next = NULL; | |
68 | } else if (*s == '<') { | |
69 | s++; | |
70 | c = (char)strtol(s, next, 0); | |
71 | if ((*next == s) || (**next != '>')) { | |
72 | c = 0; | |
73 | *next = NULL; | |
74 | } else | |
75 | (*next)++; | |
76 | } else if (*s >= 'a' && *s <= 'z') { | |
77 | c = (*s - ('a' - 1)); | |
78 | *next = s+1; | |
79 | } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) { | |
80 | c = ('@' ^ *s); | |
81 | *next = s+1; | |
82 | } else if (*s == '~') { | |
83 | c = '^'; | |
84 | *next = s+1; | |
85 | } | |
86 | } | |
87 | return c; | |
88 | } | |
89 | ||
90 | prompts_t *new_prompts(void *frontend) | |
91 | { | |
92 | prompts_t *p = snew(prompts_t); | |
93 | p->prompts = NULL; | |
94 | p->n_prompts = 0; | |
95 | p->frontend = frontend; | |
96 | p->data = NULL; | |
97 | p->to_server = TRUE; /* to be on the safe side */ | |
98 | p->name = p->instruction = NULL; | |
99 | p->name_reqd = p->instr_reqd = FALSE; | |
100 | return p; | |
101 | } | |
102 | void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len) | |
103 | { | |
104 | prompt_t *pr = snew(prompt_t); | |
105 | char *result = snewn(len, char); | |
106 | pr->prompt = promptstr; | |
107 | pr->echo = echo; | |
108 | pr->result = result; | |
109 | pr->result_len = len; | |
110 | p->n_prompts++; | |
111 | p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); | |
112 | p->prompts[p->n_prompts-1] = pr; | |
113 | } | |
114 | void free_prompts(prompts_t *p) | |
115 | { | |
116 | size_t i; | |
117 | for (i=0; i < p->n_prompts; i++) { | |
118 | prompt_t *pr = p->prompts[i]; | |
119 | memset(pr->result, 0, pr->result_len); /* burn the evidence */ | |
120 | sfree(pr->result); | |
121 | sfree(pr->prompt); | |
122 | sfree(pr); | |
123 | } | |
124 | sfree(p->prompts); | |
125 | sfree(p->name); | |
126 | sfree(p->instruction); | |
127 | sfree(p); | |
128 | } | |
129 | ||
130 | /* ---------------------------------------------------------------------- | |
131 | * String handling routines. | |
132 | */ | |
133 | ||
134 | char *dupstr(const char *s) | |
135 | { | |
136 | char *p = NULL; | |
137 | if (s) { | |
138 | int len = strlen(s); | |
139 | p = snewn(len + 1, char); | |
140 | strcpy(p, s); | |
141 | } | |
142 | return p; | |
143 | } | |
144 | ||
145 | /* Allocate the concatenation of N strings. Terminate arg list with NULL. */ | |
146 | char *dupcat(const char *s1, ...) | |
147 | { | |
148 | int len; | |
149 | char *p, *q, *sn; | |
150 | va_list ap; | |
151 | ||
152 | len = strlen(s1); | |
153 | va_start(ap, s1); | |
154 | while (1) { | |
155 | sn = va_arg(ap, char *); | |
156 | if (!sn) | |
157 | break; | |
158 | len += strlen(sn); | |
159 | } | |
160 | va_end(ap); | |
161 | ||
162 | p = snewn(len + 1, char); | |
163 | strcpy(p, s1); | |
164 | q = p + strlen(p); | |
165 | ||
166 | va_start(ap, s1); | |
167 | while (1) { | |
168 | sn = va_arg(ap, char *); | |
169 | if (!sn) | |
170 | break; | |
171 | strcpy(q, sn); | |
172 | q += strlen(q); | |
173 | } | |
174 | va_end(ap); | |
175 | ||
176 | return p; | |
177 | } | |
178 | ||
179 | /* | |
180 | * Do an sprintf(), but into a custom-allocated buffer. | |
181 | * | |
182 | * Currently I'm doing this via vsnprintf. This has worked so far, | |
183 | * but it's not good, because vsnprintf is not available on all | |
184 | * platforms. There's an ifdef to use `_vsnprintf', which seems | |
185 | * to be the local name for it on Windows. Other platforms may | |
186 | * lack it completely, in which case it'll be time to rewrite | |
187 | * this function in a totally different way. | |
188 | * | |
189 | * The only `properly' portable solution I can think of is to | |
190 | * implement my own format string scanner, which figures out an | |
191 | * upper bound for the length of each formatting directive, | |
192 | * allocates the buffer as it goes along, and calls sprintf() to | |
193 | * actually process each directive. If I ever need to actually do | |
194 | * this, some caveats: | |
195 | * | |
196 | * - It's very hard to find a reliable upper bound for | |
197 | * floating-point values. %f, in particular, when supplied with | |
198 | * a number near to the upper or lower limit of representable | |
199 | * numbers, could easily take several hundred characters. It's | |
200 | * probably feasible to predict this statically using the | |
201 | * constants in <float.h>, or even to predict it dynamically by | |
202 | * looking at the exponent of the specific float provided, but | |
203 | * it won't be fun. | |
204 | * | |
205 | * - Don't forget to _check_, after calling sprintf, that it's | |
206 | * used at most the amount of space we had available. | |
207 | * | |
208 | * - Fault any formatting directive we don't fully understand. The | |
209 | * aim here is to _guarantee_ that we never overflow the buffer, | |
210 | * because this is a security-critical function. If we see a | |
211 | * directive we don't know about, we should panic and die rather | |
212 | * than run any risk. | |
213 | */ | |
214 | char *dupprintf(const char *fmt, ...) | |
215 | { | |
216 | char *ret; | |
217 | va_list ap; | |
218 | va_start(ap, fmt); | |
219 | ret = dupvprintf(fmt, ap); | |
220 | va_end(ap); | |
221 | return ret; | |
222 | } | |
223 | char *dupvprintf(const char *fmt, va_list ap) | |
224 | { | |
225 | char *buf; | |
226 | int len, size; | |
227 | ||
228 | buf = snewn(512, char); | |
229 | size = 512; | |
230 | ||
231 | while (1) { | |
232 | #ifdef _WINDOWS | |
233 | #define vsnprintf _vsnprintf | |
234 | #endif | |
235 | #ifdef va_copy | |
236 | /* Use the `va_copy' macro mandated by C99, if present. | |
237 | * XXX some environments may have this as __va_copy() */ | |
238 | va_list aq; | |
239 | va_copy(aq, ap); | |
240 | len = vsnprintf(buf, size, fmt, aq); | |
241 | va_end(aq); | |
242 | #else | |
243 | /* Ugh. No va_copy macro, so do something nasty. | |
244 | * Technically, you can't reuse a va_list like this: it is left | |
245 | * unspecified whether advancing a va_list pointer modifies its | |
246 | * value or something it points to, so on some platforms calling | |
247 | * vsnprintf twice on the same va_list might fail hideously | |
248 | * (indeed, it has been observed to). | |
249 | * XXX the autoconf manual suggests that using memcpy() will give | |
250 | * "maximum portability". */ | |
251 | len = vsnprintf(buf, size, fmt, ap); | |
252 | #endif | |
253 | if (len >= 0 && len < size) { | |
254 | /* This is the C99-specified criterion for snprintf to have | |
255 | * been completely successful. */ | |
256 | return buf; | |
257 | } else if (len > 0) { | |
258 | /* This is the C99 error condition: the returned length is | |
259 | * the required buffer size not counting the NUL. */ | |
260 | size = len + 1; | |
261 | } else { | |
262 | /* This is the pre-C99 glibc error condition: <0 means the | |
263 | * buffer wasn't big enough, so we enlarge it a bit and hope. */ | |
264 | size += 512; | |
265 | } | |
266 | buf = sresize(buf, size, char); | |
267 | } | |
268 | } | |
269 | ||
270 | /* | |
271 | * Read an entire line of text from a file. Return a buffer | |
272 | * malloced to be as big as necessary (caller must free). | |
273 | */ | |
274 | char *fgetline(FILE *fp) | |
275 | { | |
276 | char *ret = snewn(512, char); | |
277 | int size = 512, len = 0; | |
278 | while (fgets(ret + len, size - len, fp)) { | |
279 | len += strlen(ret + len); | |
280 | if (ret[len-1] == '\n') | |
281 | break; /* got a newline, we're done */ | |
282 | size = len + 512; | |
283 | ret = sresize(ret, size, char); | |
284 | } | |
285 | if (len == 0) { /* first fgets returned NULL */ | |
286 | sfree(ret); | |
287 | return NULL; | |
288 | } | |
289 | ret[len] = '\0'; | |
290 | return ret; | |
291 | } | |
292 | ||
293 | /* ---------------------------------------------------------------------- | |
294 | * Base64 encoding routine. This is required in public-key writing | |
295 | * but also in HTTP proxy handling, so it's centralised here. | |
296 | */ | |
297 | ||
298 | void base64_encode_atom(unsigned char *data, int n, char *out) | |
299 | { | |
300 | static const char base64_chars[] = | |
301 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
302 | ||
303 | unsigned word; | |
304 | ||
305 | word = data[0] << 16; | |
306 | if (n > 1) | |
307 | word |= data[1] << 8; | |
308 | if (n > 2) | |
309 | word |= data[2]; | |
310 | out[0] = base64_chars[(word >> 18) & 0x3F]; | |
311 | out[1] = base64_chars[(word >> 12) & 0x3F]; | |
312 | if (n > 1) | |
313 | out[2] = base64_chars[(word >> 6) & 0x3F]; | |
314 | else | |
315 | out[2] = '='; | |
316 | if (n > 2) | |
317 | out[3] = base64_chars[word & 0x3F]; | |
318 | else | |
319 | out[3] = '='; | |
320 | } | |
321 | ||
322 | /* ---------------------------------------------------------------------- | |
323 | * Generic routines to deal with send buffers: a linked list of | |
324 | * smallish blocks, with the operations | |
325 | * | |
326 | * - add an arbitrary amount of data to the end of the list | |
327 | * - remove the first N bytes from the list | |
328 | * - return a (pointer,length) pair giving some initial data in | |
329 | * the list, suitable for passing to a send or write system | |
330 | * call | |
331 | * - retrieve a larger amount of initial data from the list | |
332 | * - return the current size of the buffer chain in bytes | |
333 | */ | |
334 | ||
335 | #define BUFFER_GRANULE 512 | |
336 | ||
337 | struct bufchain_granule { | |
338 | struct bufchain_granule *next; | |
339 | int buflen, bufpos; | |
340 | char buf[BUFFER_GRANULE]; | |
341 | }; | |
342 | ||
343 | void bufchain_init(bufchain *ch) | |
344 | { | |
345 | ch->head = ch->tail = NULL; | |
346 | ch->buffersize = 0; | |
347 | } | |
348 | ||
349 | void bufchain_clear(bufchain *ch) | |
350 | { | |
351 | struct bufchain_granule *b; | |
352 | while (ch->head) { | |
353 | b = ch->head; | |
354 | ch->head = ch->head->next; | |
355 | sfree(b); | |
356 | } | |
357 | ch->tail = NULL; | |
358 | ch->buffersize = 0; | |
359 | } | |
360 | ||
361 | int bufchain_size(bufchain *ch) | |
362 | { | |
363 | return ch->buffersize; | |
364 | } | |
365 | ||
366 | void bufchain_add(bufchain *ch, const void *data, int len) | |
367 | { | |
368 | const char *buf = (const char *)data; | |
369 | ||
370 | if (len == 0) return; | |
371 | ||
372 | ch->buffersize += len; | |
373 | ||
374 | if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { | |
375 | int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen); | |
376 | memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); | |
377 | buf += copylen; | |
378 | len -= copylen; | |
379 | ch->tail->buflen += copylen; | |
380 | } | |
381 | while (len > 0) { | |
382 | int grainlen = min(len, BUFFER_GRANULE); | |
383 | struct bufchain_granule *newbuf; | |
384 | newbuf = snew(struct bufchain_granule); | |
385 | newbuf->bufpos = 0; | |
386 | newbuf->buflen = grainlen; | |
387 | memcpy(newbuf->buf, buf, grainlen); | |
388 | buf += grainlen; | |
389 | len -= grainlen; | |
390 | if (ch->tail) | |
391 | ch->tail->next = newbuf; | |
392 | else | |
393 | ch->head = ch->tail = newbuf; | |
394 | newbuf->next = NULL; | |
395 | ch->tail = newbuf; | |
396 | } | |
397 | } | |
398 | ||
399 | void bufchain_consume(bufchain *ch, int len) | |
400 | { | |
401 | struct bufchain_granule *tmp; | |
402 | ||
403 | assert(ch->buffersize >= len); | |
404 | while (len > 0) { | |
405 | int remlen = len; | |
406 | assert(ch->head != NULL); | |
407 | if (remlen >= ch->head->buflen - ch->head->bufpos) { | |
408 | remlen = ch->head->buflen - ch->head->bufpos; | |
409 | tmp = ch->head; | |
410 | ch->head = tmp->next; | |
411 | sfree(tmp); | |
412 | if (!ch->head) | |
413 | ch->tail = NULL; | |
414 | } else | |
415 | ch->head->bufpos += remlen; | |
416 | ch->buffersize -= remlen; | |
417 | len -= remlen; | |
418 | } | |
419 | } | |
420 | ||
421 | void bufchain_prefix(bufchain *ch, void **data, int *len) | |
422 | { | |
423 | *len = ch->head->buflen - ch->head->bufpos; | |
424 | *data = ch->head->buf + ch->head->bufpos; | |
425 | } | |
426 | ||
427 | void bufchain_fetch(bufchain *ch, void *data, int len) | |
428 | { | |
429 | struct bufchain_granule *tmp; | |
430 | char *data_c = (char *)data; | |
431 | ||
432 | tmp = ch->head; | |
433 | ||
434 | assert(ch->buffersize >= len); | |
435 | while (len > 0) { | |
436 | int remlen = len; | |
437 | ||
438 | assert(tmp != NULL); | |
439 | if (remlen >= tmp->buflen - tmp->bufpos) | |
440 | remlen = tmp->buflen - tmp->bufpos; | |
441 | memcpy(data_c, tmp->buf + tmp->bufpos, remlen); | |
442 | ||
443 | tmp = tmp->next; | |
444 | len -= remlen; | |
445 | data_c += remlen; | |
446 | } | |
447 | } | |
448 | ||
449 | /* ---------------------------------------------------------------------- | |
450 | * My own versions of malloc, realloc and free. Because I want | |
451 | * malloc and realloc to bomb out and exit the program if they run | |
452 | * out of memory, realloc to reliably call malloc if passed a NULL | |
453 | * pointer, and free to reliably do nothing if passed a NULL | |
454 | * pointer. We can also put trace printouts in, if we need to; and | |
455 | * we can also replace the allocator with an ElectricFence-like | |
456 | * one. | |
457 | */ | |
458 | ||
459 | #ifdef MINEFIELD | |
460 | void *minefield_c_malloc(size_t size); | |
461 | void minefield_c_free(void *p); | |
462 | void *minefield_c_realloc(void *p, size_t size); | |
463 | #endif | |
464 | ||
465 | #ifdef MALLOC_LOG | |
466 | static FILE *fp = NULL; | |
467 | ||
468 | static char *mlog_file = NULL; | |
469 | static int mlog_line = 0; | |
470 | ||
471 | void mlog(char *file, int line) | |
472 | { | |
473 | mlog_file = file; | |
474 | mlog_line = line; | |
475 | if (!fp) { | |
476 | fp = fopen("putty_mem.log", "w"); | |
477 | setvbuf(fp, NULL, _IONBF, BUFSIZ); | |
478 | } | |
479 | if (fp) | |
480 | fprintf(fp, "%s:%d: ", file, line); | |
481 | } | |
482 | #endif | |
483 | ||
484 | void *safemalloc(size_t n, size_t size) | |
485 | { | |
486 | void *p; | |
487 | ||
488 | if (n > INT_MAX / size) { | |
489 | p = NULL; | |
490 | } else { | |
491 | size *= n; | |
492 | if (size == 0) size = 1; | |
493 | #ifdef MINEFIELD | |
494 | p = minefield_c_malloc(size); | |
495 | #else | |
496 | p = malloc(size); | |
497 | #endif | |
498 | } | |
499 | ||
500 | if (!p) { | |
501 | char str[200]; | |
502 | #ifdef MALLOC_LOG | |
503 | sprintf(str, "Out of memory! (%s:%d, size=%d)", | |
504 | mlog_file, mlog_line, size); | |
505 | fprintf(fp, "*** %s\n", str); | |
506 | fclose(fp); | |
507 | #else | |
508 | strcpy(str, "Out of memory!"); | |
509 | #endif | |
510 | modalfatalbox(str); | |
511 | } | |
512 | #ifdef MALLOC_LOG | |
513 | if (fp) | |
514 | fprintf(fp, "malloc(%d) returns %p\n", size, p); | |
515 | #endif | |
516 | return p; | |
517 | } | |
518 | ||
519 | void *saferealloc(void *ptr, size_t n, size_t size) | |
520 | { | |
521 | void *p; | |
522 | ||
523 | if (n > INT_MAX / size) { | |
524 | p = NULL; | |
525 | } else { | |
526 | size *= n; | |
527 | if (!ptr) { | |
528 | #ifdef MINEFIELD | |
529 | p = minefield_c_malloc(size); | |
530 | #else | |
531 | p = malloc(size); | |
532 | #endif | |
533 | } else { | |
534 | #ifdef MINEFIELD | |
535 | p = minefield_c_realloc(ptr, size); | |
536 | #else | |
537 | p = realloc(ptr, size); | |
538 | #endif | |
539 | } | |
540 | } | |
541 | ||
542 | if (!p) { | |
543 | char str[200]; | |
544 | #ifdef MALLOC_LOG | |
545 | sprintf(str, "Out of memory! (%s:%d, size=%d)", | |
546 | mlog_file, mlog_line, size); | |
547 | fprintf(fp, "*** %s\n", str); | |
548 | fclose(fp); | |
549 | #else | |
550 | strcpy(str, "Out of memory!"); | |
551 | #endif | |
552 | modalfatalbox(str); | |
553 | } | |
554 | #ifdef MALLOC_LOG | |
555 | if (fp) | |
556 | fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p); | |
557 | #endif | |
558 | return p; | |
559 | } | |
560 | ||
561 | void safefree(void *ptr) | |
562 | { | |
563 | if (ptr) { | |
564 | #ifdef MALLOC_LOG | |
565 | if (fp) | |
566 | fprintf(fp, "free(%p)\n", ptr); | |
567 | #endif | |
568 | #ifdef MINEFIELD | |
569 | minefield_c_free(ptr); | |
570 | #else | |
571 | free(ptr); | |
572 | #endif | |
573 | } | |
574 | #ifdef MALLOC_LOG | |
575 | else if (fp) | |
576 | fprintf(fp, "freeing null pointer - no action taken\n"); | |
577 | #endif | |
578 | } | |
579 | ||
580 | /* ---------------------------------------------------------------------- | |
581 | * Debugging routines. | |
582 | */ | |
583 | ||
584 | #ifdef DEBUG | |
585 | extern void dputs(char *); /* defined in per-platform *misc.c */ | |
586 | ||
587 | void debug_printf(char *fmt, ...) | |
588 | { | |
589 | char *buf; | |
590 | va_list ap; | |
591 | ||
592 | va_start(ap, fmt); | |
593 | buf = dupvprintf(fmt, ap); | |
594 | dputs(buf); | |
595 | sfree(buf); | |
596 | va_end(ap); | |
597 | } | |
598 | ||
599 | ||
600 | void debug_memdump(void *buf, int len, int L) | |
601 | { | |
602 | int i; | |
603 | unsigned char *p = buf; | |
604 | char foo[17]; | |
605 | if (L) { | |
606 | int delta; | |
607 | debug_printf("\t%d (0x%x) bytes:\n", len, len); | |
608 | delta = 15 & (unsigned long int) p; | |
609 | p -= delta; | |
610 | len += delta; | |
611 | } | |
612 | for (; 0 < len; p += 16, len -= 16) { | |
613 | dputs(" "); | |
614 | if (L) | |
615 | debug_printf("%p: ", p); | |
616 | strcpy(foo, "................"); /* sixteen dots */ | |
617 | for (i = 0; i < 16 && i < len; ++i) { | |
618 | if (&p[i] < (unsigned char *) buf) { | |
619 | dputs(" "); /* 3 spaces */ | |
620 | foo[i] = ' '; | |
621 | } else { | |
622 | debug_printf("%c%02.2x", | |
623 | &p[i] != (unsigned char *) buf | |
624 | && i % 4 ? '.' : ' ', p[i] | |
625 | ); | |
626 | if (p[i] >= ' ' && p[i] <= '~') | |
627 | foo[i] = (char) p[i]; | |
628 | } | |
629 | } | |
630 | foo[i] = '\0'; | |
631 | debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); | |
632 | } | |
633 | } | |
634 | ||
635 | #endif /* def DEBUG */ | |
636 | ||
637 | /* | |
638 | * Determine whether or not a Config structure represents a session | |
639 | * which can sensibly be launched right now. | |
640 | */ | |
641 | int cfg_launchable(const Config *cfg) | |
642 | { | |
643 | if (cfg->protocol == PROT_SERIAL) | |
644 | return cfg->serline[0] != 0; | |
645 | else | |
646 | return cfg->host[0] != 0; | |
647 | } | |
648 | ||
649 | char const *cfg_dest(const Config *cfg) | |
650 | { | |
651 | if (cfg->protocol == PROT_SERIAL) | |
652 | return cfg->serline; | |
653 | else | |
654 | return cfg->host; | |
655 | } |