1 /* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
2  *
3  * distcc -- A simple distributed compiler system
4  *
5  * Copyright (C) 2002, 2003, 2004, 2009 by Martin Pool <mbp@samba.org>
6  * Copyright 2004 Google Inc.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
21  * USA.
22  */
23 
24 /*  dcc_randomize_host_list() and friends:
25  *   Author: Josh Hyman <joshh@google.com>
26  */
27 
28 
29                 /* The lyf so short, the craft so long to lerne.
30                  * -- Chaucer */
31 
32 
33 
34 /**
35  * @file
36  *
37  * Routines to parse <tt>$DISTCC_HOSTS</tt>.  Actual decisions about
38  * where to run a job are in where.c.
39  *
40  * The grammar of this variable is, informally:
41  *
42   DISTCC_HOSTS = HOSTSPEC ...
43   HOSTSPEC = LOCAL_HOST | SSH_HOST | TCP_HOST | OLDSTYLE_TCP_HOST
44                         | GLOBAL_OPTION
45   LOCAL_HOST = localhost[/LIMIT]
46   SSH_HOST = [USER]@HOSTID[/LIMIT][:COMMAND][OPTIONS]
47   TCP_HOST = HOSTID[:PORT][/LIMIT][OPTIONS]
48   OLDSTYLE_TCP_HOST = HOSTID[/LIMIT][:PORT][OPTIONS]
49   HOSTID = HOSTNAME | IPV4
50   OPTIONS = ,OPTION[OPTIONS]
51   OPTION = lzo | cpp
52   GLOBAL_OPTION = --randomize
53  *
54  * Any amount of whitespace may be present between hosts.
55  *
56  * The command specified for SSH defines the location of the remote
57  * server, e.g. "/usr/local/bin/distccd".  This is provided as a
58  * convenience who have trouble getting their PATH set correctly for
59  * sshd to find distccd, and should not normally be needed.
60  *
61  * If you need to specify special options for ssh, they should be put
62  * in ~/.ssh/config and referenced by the hostname.
63  *
64  * The TCP port defaults to 3632 and should not normally need to be
65  * overridden.
66  *
67  * IPv6 literals are not supported yet.  They will need to be
68  * surrounded by square brackets because they may contain a colon,
69  * which would otherwise be ambiguous.  This is consistent with other
70  * URL-like schemes.
71  */
72 
73 
74 /*
75        Alexandre Oliva writes
76 
77         I take this opportunity to plead people to consider such issues when
78         proposing additional syntax for DISTCC_HOSTS: if it was possible to
79         handle DISTCC_HOSTS as a single shell word (perhaps after turning
80         blanks into say commas), without the risk of any shell active
81         characters such as {, }, ~, $, quotes getting in the way, outputting
82         distcc commands that override DISTCC_HOSTS would be far
83         simpler.
84 
85   TODO: Perhaps entries in the host list that "look like files" (start
86     with '/' or '~') should be read in as files?  This could even be
87     recursive.
88 */
89 
90 #include <config.h>
91 
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <unistd.h>
95 #include <string.h>
96 #include <errno.h>
97 #include <time.h>
98 #include <ctype.h>
99 #include <sys/time.h>
100 #include <sys/types.h>
101 
102 #include "distcc.h"
103 #include "trace.h"
104 #include "util.h"
105 #include "hosts.h"
106 #include "exitcode.h"
107 #include "snprintf.h"
108 #include "config.h"
109 #ifdef HAVE_AVAHI
110 #include "zeroconf.h"
111 #define ZEROCONF_MAGIC "+zeroconf"
112 #endif
113 
114 const int dcc_default_port = DISTCC_DEFAULT_PORT;
115 
116 /***
117  * A simple container which would hold a host -> rand int pair
118  ***/
119 struct rand_container {
120     struct dcc_hostdef *host;
121     int rand;
122 };
123 
124 int dcc_randomize_host_list(struct dcc_hostdef **host_list, int length);
125 
126 int dcc_compare_container(const void *a, const void *b);
127 
128 
129 #ifndef HAVE_STRNDUP
130 /**
131  * Copy at most @p size characters from @p src, plus a terminating nul.
132  *
133  * Really this needs to be in util.c, but it's only used here.
134  **/
135 char *strndup(const char *src, size_t size);
strndup(const char * src,size_t size)136 char *strndup(const char *src, size_t size)
137 {
138     char *dst;
139 
140     dst = malloc(size + 1);
141     if (dst == NULL)
142         return NULL;
143     strncpy(dst, src, size);
144     dst[size] = '\0';
145 
146     return dst;
147 }
148 #endif
149 
150 /**
151  * Get a list of hosts to use.
152  *
153  * Hosts are taken from DISTCC_HOSTS, if that exists.  Otherwise, they are
154  * taken from $DISTCC_DIR/hosts, if that exists.  Otherwise, they are taken
155  * from ${sysconfdir}/distcc/hosts, if that exists.  Otherwise, we fail.
156  **/
dcc_get_hostlist(struct dcc_hostdef ** ret_list,int * ret_nhosts)157 int dcc_get_hostlist(struct dcc_hostdef **ret_list,
158                      int *ret_nhosts)
159 {
160     char *env;
161     char *path, *top;
162     int ret;
163 
164     *ret_list = NULL;
165     *ret_nhosts = 0;
166 
167     if ((env = getenv("DISTCC_HOSTS")) != NULL) {
168         rs_trace("read hosts from environment");
169         return dcc_parse_hosts(env, "$DISTCC_HOSTS", ret_list, ret_nhosts, NULL);
170     }
171 
172     /* $DISTCC_DIR or ~/.distcc */
173     if ((ret = dcc_get_top_dir(&top)) == 0) {
174         /* if we failed to get it, just warn */
175 
176         checked_asprintf(&path, "%s/hosts", top);
177         if (path != NULL && access(path, R_OK) == 0) {
178             ret = dcc_parse_hosts_file(path, ret_list, ret_nhosts);
179             free(path);
180             return ret;
181         } else {
182             rs_trace("not reading %s: %s", path, strerror(errno));
183             free(path);
184         }
185     }
186 
187     checked_asprintf(&path, "%s/distcc/hosts", SYSCONFDIR);
188     if (path != NULL && access(path, R_OK) == 0) {
189         ret = dcc_parse_hosts_file(path, ret_list, ret_nhosts);
190         free(path);
191         return ret;
192     } else {
193         rs_trace("not reading %s: %s", path, strerror(errno));
194         free(path);
195     }
196 
197     /* FIXME: Clearer message? */
198     rs_log_warning("no hostlist is set; can't distribute work");
199 
200     return EXIT_BAD_HOSTSPEC;
201 }
202 
203 
204 /**
205  * Parse an optionally present multiplier.
206  *
207  * *psrc is the current parse cursor; it is advanced over what is read.
208  *
209  * If a multiplier is present, *psrc points to a substring starting with '/'.
210  * The host definition is updated to the numeric value following.  Otherwise
211  * the hostdef is unchanged.
212  **/
dcc_parse_multiplier(const char ** psrc,struct dcc_hostdef * hostdef)213 static int dcc_parse_multiplier(const char **psrc, struct dcc_hostdef *hostdef)
214 {
215     const char *token = *psrc;
216 
217     if ((*psrc)[0] == '/' || (*psrc)[0] == '=') {
218         int val;
219         (*psrc)++;
220         val = atoi(*psrc);
221         if (val == 0) {
222             rs_log_error("bad multiplier \"%s\" in host specification", token);
223             return EXIT_BAD_HOSTSPEC;
224         }
225         while (isdigit((uint8_t)**psrc))
226             (*psrc)++;
227         hostdef->n_slots = val;
228     }
229     return 0;
230 }
231 
232 
233 /**
234  * Parse an optionally present option string.
235  *
236  * At the moment the only two options we have is "lzo" for compression,
237  * and "cpp" if the server supports doing the preprocessing there, also.
238  **/
dcc_parse_options(const char ** psrc,struct dcc_hostdef * host)239 static int dcc_parse_options(const char **psrc,
240                              struct dcc_hostdef *host)
241 {
242     const char *started = *psrc, *p = *psrc;
243 
244     host->compr = DCC_COMPRESS_NONE;
245     host->cpp_where = DCC_CPP_ON_CLIENT;
246 #ifdef HAVE_GSSAPI
247     host->authenticate = 0;
248     host->auth_name = NULL;
249 #endif
250 
251     while (p[0] == ',') {
252         p++;
253         if (str_startswith("lzo", p)) {
254             rs_trace("got LZO option");
255             host->compr = DCC_COMPRESS_LZO1X;
256             p += 3;
257         } else if (str_startswith("down", p)) {
258             /* if "hostid,down", mark it down, and strip down from hostname */
259             host->is_up = 0;
260             p += 4;
261         } else if (str_startswith("cpp", p)) {
262             rs_trace("got CPP option");
263             host->cpp_where = DCC_CPP_ON_SERVER;
264             p += 3;
265 #ifdef HAVE_GSSAPI
266         } else if (str_startswith("auth", p)) {
267             rs_trace("got GSSAPI option");
268             host->authenticate = 1;
269             p += 4;
270             if (p[0] == '=') {
271                 p++;
272                 int ret;
273                 if ((ret = dcc_dup_part(&p, &host->auth_name, "/: \t\n\r\f,")))
274 	                return ret;
275 
276                 if (host->auth_name) {
277                     rs_trace("using \"%s\" server name instead of fqdn "
278                              "lookup for GSS-API auth", host->auth_name);
279                 }
280             }
281 #endif
282         } else {
283             rs_log_error("unrecognized option in host specification: %s",
284                          started);
285             return EXIT_BAD_HOSTSPEC;
286         }
287     }
288     if (dcc_get_protover_from_features(host->compr, host->cpp_where,
289                                        &host->protover) == -1) {
290         rs_log_error("invalid host options: %s", started);
291         return EXIT_BAD_HOSTSPEC;
292     }
293 
294     *psrc = p;
295 
296     return 0;
297 }
298 
dcc_parse_ssh_host(struct dcc_hostdef * hostdef,const char * token_start)299 static int dcc_parse_ssh_host(struct dcc_hostdef *hostdef,
300                               const char *token_start)
301 {
302     int ret;
303     const char *token = token_start;
304 
305     /* Everything up to '@' is the username */
306     if ((ret = dcc_dup_part(&token, &hostdef->user, "@")) != 0)
307         return ret;
308 
309     if (token[0] != '@') {
310         rs_log_error("expected '@' to start ssh token");
311         return EXIT_BAD_HOSTSPEC;
312     }
313 
314     token++;
315 
316     if ((ret = dcc_dup_part(&token, &hostdef->hostname, "/: \t\n\r\f,")) != 0)
317         return ret;
318 
319     if (!hostdef->hostname) {
320         rs_log_error("hostname is required in SSH host specification \"%s\"",
321                      token_start);
322         return EXIT_BAD_HOSTSPEC;
323     }
324 
325     if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
326         return ret;
327 
328     if (token[0] == ':') {
329         token++;
330         if ((ret = dcc_dup_part(&token, &hostdef->ssh_command, " \t\n\r\f,")))
331             return ret;
332     }
333 
334     if ((ret = dcc_parse_options(&token, hostdef)))
335         return ret;
336 
337     hostdef->mode = DCC_MODE_SSH;
338     return 0;
339 }
340 
341 
dcc_parse_tcp_host(struct dcc_hostdef * hostdef,const char * const token_start)342 static int dcc_parse_tcp_host(struct dcc_hostdef *hostdef,
343                               const char * const token_start)
344 {
345     int ret;
346     const char *token = token_start;
347 
348     if (token[0] == '[') {
349 	/* Skip the leading bracket, which is not part of the address */
350 	token++;
351 
352 	/* We have an IPv6 Address */
353 	if ((ret = dcc_dup_part(&token, &hostdef->hostname, "/] \t\n\r\f,")))
354 	    return ret;
355 	if(token[0] != ']') {
356 	    rs_log_error("IPv6 Hostname requires closing ']'");
357 	    return EXIT_BAD_HOSTSPEC;
358 	}
359 	token++;
360 
361     } else {
362 	/* Parse IPv4 address */
363 	if ((ret = dcc_dup_part(&token, &hostdef->hostname, "/: \t\n\r\f,")))
364 	    return ret;
365     }
366 
367     if (!hostdef->hostname) {
368         rs_log_error("hostname is required in tcp host specification \"%s\"",
369                      token_start);
370         return EXIT_BAD_HOSTSPEC;
371     }
372 
373     if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
374         return ret;
375 
376     hostdef->port = dcc_default_port;
377     if (token[0] == ':') {
378         char *tail;
379 
380         token++;
381 
382         hostdef->port = strtol(token, &tail, 10);
383         if (*tail != '\0' && !isspace((uint8_t)*tail) && *tail != '/' && *tail != ',') {
384             rs_log_error("invalid tcp port specification in \"%s\"", token);
385             return EXIT_BAD_HOSTSPEC;
386         } else {
387             token = tail;
388         }
389     }
390 
391     if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
392         return ret;
393 
394     if ((ret = dcc_parse_options(&token, hostdef)))
395         return ret;
396 
397     hostdef->mode = DCC_MODE_TCP;
398     return 0;
399 }
400 
401 
dcc_parse_localhost(struct dcc_hostdef * hostdef,const char * token_start)402 static int dcc_parse_localhost(struct dcc_hostdef *hostdef,
403                                const char * token_start)
404 {
405     const char *token = token_start + strlen("localhost");
406 
407     hostdef->mode = DCC_MODE_LOCAL;
408     hostdef->hostname = strdup("localhost");
409 
410     /* Run only two tasks on localhost by default.
411      *
412      * It might be nice to run more if there are more CPUs, but determining
413      * the number of CPUs on Linux is a bit expensive since it requires
414      * examining mtab and /proc/stat.  Anyone lucky enough to have a >2 CPU
415      * machine can specify a number in the host list.
416      */
417     hostdef->n_slots = 2;
418 
419     return dcc_parse_multiplier(&token, hostdef);
420 }
421 
422 /** Given a host with its protover fields set, set
423  *  its feature fields appropriately. Returns 0 if the protocol
424  *  is known, non-zero otherwise.
425  */
dcc_get_features_from_protover(enum dcc_protover protover,enum dcc_compress * compr,enum dcc_cpp_where * cpp_where)426 int dcc_get_features_from_protover(enum dcc_protover protover,
427                                    enum dcc_compress *compr,
428                                    enum dcc_cpp_where *cpp_where)
429 {
430     if (protover > 1) {
431         *compr = DCC_COMPRESS_LZO1X;
432     } else {
433         *compr = DCC_COMPRESS_NONE;
434     }
435     if (protover > 2) {
436         *cpp_where = DCC_CPP_ON_SERVER;
437     } else {
438         *cpp_where = DCC_CPP_ON_CLIENT;
439     }
440 
441     if (protover == 0 || protover > 3) {
442         return 1;
443     } else {
444         return 0;
445     }
446 }
447 
448 /** Given a host with its feature fields set, set
449  *  its protover appropriately. Return the protover,
450  *  or -1 on error.
451  */
dcc_get_protover_from_features(enum dcc_compress compr,enum dcc_cpp_where cpp_where,enum dcc_protover * protover)452 int dcc_get_protover_from_features(enum dcc_compress compr,
453                                    enum dcc_cpp_where cpp_where,
454                                    enum dcc_protover *protover)
455 {
456     *protover = -1;
457 
458     if (compr == DCC_COMPRESS_NONE && cpp_where == DCC_CPP_ON_CLIENT) {
459         *protover = DCC_VER_1;
460     }
461 
462     if (compr == DCC_COMPRESS_LZO1X && cpp_where == DCC_CPP_ON_SERVER) {
463         *protover = DCC_VER_3;
464     }
465 
466     if (compr == DCC_COMPRESS_LZO1X && cpp_where == DCC_CPP_ON_CLIENT) {
467         *protover = DCC_VER_2;
468     }
469 
470     if (compr == DCC_COMPRESS_NONE && cpp_where == DCC_CPP_ON_SERVER) {
471         rs_log_error("pump mode (',cpp') requires compression (',lzo')");
472     }
473 
474     return *protover;
475 }
476 
477 
478 /**
479  * @p where is the host list, taken either from the environment or file.
480  *
481  * @return 0 if parsed successfully; nonzero if there were any errors,
482  * or if no hosts were defined.
483  **/
dcc_parse_hosts(const char * where,const char * source_name,struct dcc_hostdef ** ret_list,int * ret_nhosts,struct dcc_hostdef ** ret_prev)484 int dcc_parse_hosts(const char *where, const char *source_name,
485                     struct dcc_hostdef **ret_list,
486                     int *ret_nhosts, struct dcc_hostdef **ret_prev)
487 {
488     int ret, flag_randomize = 0;
489     struct dcc_hostdef *curr, *_prev;
490 
491     if (!ret_prev) {
492         ret_prev = &_prev;
493         _prev = NULL;
494     }
495 
496     /* TODO: Check for '/' in places where it might cause trouble with
497      * a lock file name. */
498 
499     /* A simple, hardcoded scanner.  Some of the GNU routines might be
500      * useful here, but they won't work on less capable systems.
501      *
502      * We repeatedly attempt to extract a whitespace-delimited host
503      * definition from the string until none remain.  Allocate an
504      * entry; hook to previous entry.  We then determine if there is a
505      * '@' in it, which tells us whether it is an SSH or TCP
506      * definition.  We then duplicate the relevant subcomponents into
507      * the relevant fields. */
508     while (1) {
509         int token_len;
510         const char *token_start;
511         int has_at;
512 
513         if (where[0] == '\0')
514             break;              /* end of string */
515 
516         /* skip over comments */
517         if (where[0] == '#') {
518             do
519                 where++;
520             while (where[0] != '\n' && where[0] != '\r' && where[0] != '\0');
521             continue;
522         }
523 
524         if (isspace((uint8_t)where[0])) {
525             where++;            /* skip space */
526             continue;
527         }
528 
529         token_start = where;
530         token_len = strcspn(where, " #\t\n\f\r");
531 
532         /* intercept keywords which are not actually hosts */
533         if (!strncmp(token_start, "--randomize", 11)) {
534             flag_randomize = 1;
535             where = token_start + token_len;
536             continue;
537         }
538 
539         if(!strncmp(token_start, "--localslots_cpp", 16)) {
540             const char *ptr;
541             ptr = token_start + 16;
542             if(dcc_parse_multiplier(&ptr, dcc_hostdef_local_cpp) == 0) {
543                 where = token_start + token_len;
544                 continue;
545             }
546         }
547 
548         if(!strncmp(token_start, "--localslots", 12)) {
549             const char *ptr;
550             ptr = token_start + 12;
551             if(dcc_parse_multiplier(&ptr, dcc_hostdef_local) == 0) {
552                 where = token_start + token_len;
553                 continue;
554             }
555         }
556 
557 #ifdef HAVE_AVAHI
558         if (token_len == sizeof(ZEROCONF_MAGIC)-1 &&
559             !strncmp(token_start, ZEROCONF_MAGIC, (unsigned) token_len)) {
560             if ((ret = dcc_zeroconf_add_hosts(ret_list, ret_nhosts, 4, ret_prev) != 0))
561                 return ret;
562             goto skip;
563         }
564 #endif
565 
566         /* Allocate new list item */
567         curr = calloc(1, sizeof(struct dcc_hostdef));
568         if (!curr) {
569             rs_log_crit("failed to allocate host definition");
570             return EXIT_OUT_OF_MEMORY;
571         }
572 
573         /* by default, mark the host up */
574         curr->is_up = 1;
575 
576         /* Store verbatim hostname */
577         if (!(curr->hostdef_string = strndup(token_start, (size_t) token_len))) {
578             rs_log_crit("failed to allocate hostdef_string");
579             return EXIT_OUT_OF_MEMORY;
580         }
581 
582         /* Link into list */
583         if (*ret_prev) {
584             (*ret_prev)->next = curr;
585         } else {
586             *ret_list = curr;   /* first */
587         }
588 
589         /* Default task limit.  A bit higher than the local limit to allow for
590          * some files in transit. */
591         curr->n_slots = 4;
592 
593         curr->protover = DCC_VER_1; /* default */
594         curr->compr = DCC_COMPRESS_NONE;
595 
596         has_at = (memchr(token_start, '@', (size_t) token_len) != NULL);
597 
598         if (!strncmp(token_start, "localhost", 9)
599             && (token_len == 9 || token_start[9] == '/')) {
600             rs_trace("found localhost token \"%.*s\"", token_len, token_start);
601             if ((ret = dcc_parse_localhost(curr, token_start)) != 0)
602                 return ret;
603         } else if (has_at) {
604             rs_trace("found ssh token \"%.*s\"", token_len, token_start);
605             if ((ret = dcc_parse_ssh_host(curr, token_start)) != 0)
606                 return ret;
607         } else {
608             rs_trace("found tcp token \"%.*s\"", token_len, token_start);
609             if ((ret = dcc_parse_tcp_host(curr, token_start)) != 0)
610                 return ret;
611         }
612 
613         if (!curr->is_up) {
614             rs_trace("host %s is down", curr->hostdef_string);
615         }
616 
617         (*ret_nhosts)++;
618         *ret_prev = curr;
619 
620 #ifdef HAVE_AVAHI
621         skip:
622 #endif
623 
624         /* continue to next token if any */
625         where = token_start + token_len;
626     }
627 
628     if (*ret_nhosts) {
629         if (flag_randomize)
630             if ((ret = dcc_randomize_host_list(ret_list, *ret_nhosts)) != 0)
631                 return ret;
632         return 0;
633     } else {
634         rs_log_warning("%s contained no hosts; can't distribute work", source_name);
635         return EXIT_BAD_HOSTSPEC;
636     }
637 }
638 
639 
dcc_compare_container(const void * a,const void * b)640 int dcc_compare_container(const void *a, const void *b)
641 {
642     struct rand_container *i, *j;
643     i = (struct rand_container *) a;
644     j = (struct rand_container *) b;
645 
646     if (i->rand == j->rand)
647         return 0;
648     else if (i->rand > j->rand)
649         return 1;
650     else
651         return -1;
652 }
653 
dcc_randomize_host_list(struct dcc_hostdef ** host_list,int length)654 int dcc_randomize_host_list(struct dcc_hostdef **host_list, int length)
655 {
656     int i;
657     unsigned int rand_val;
658     struct dcc_hostdef *curr;
659     struct rand_container *c;
660 
661     c = malloc(length * sizeof(struct rand_container));
662     if (!c) {
663         rs_log_crit("failed to allocate host definition");
664         return EXIT_OUT_OF_MEMORY;
665     }
666 /*
667 {
668 #ifdef HAVE_GETTIMEOFDAY
669     int ret;
670     struct timeval tv;
671     if ((ret = gettimeofday(&tv, NULL)) == 0)
672         rand_val = (unsigned int) tv.tv_usec;
673     else
674 #else
675         rand_val = (unsigned int) time(NULL) ^ (unsigned int) getpid();
676 #endif
677 }
678 */
679     rand_val = (unsigned int) getpid();
680 
681     /* create pairs of hosts -> random numbers */
682     srand(rand_val);
683     curr = *host_list;
684     for (i = 0; i < length; i++) {
685         c[i].host = curr;
686         c[i].rand = rand();
687         curr = curr->next;
688     }
689 
690     /* sort */
691     qsort(c, length, sizeof(struct rand_container), &dcc_compare_container);
692 
693     /* reorder the list */
694     for (i = 0; i < length; i++) {
695         if (i != length - 1)
696             c[i].host->next = c[i+1].host;
697         else
698             c[i].host->next = NULL;
699     }
700 
701     /* move the start of the list */
702     *host_list = c[0].host;
703 
704     free(c);
705     return 0;
706 }
707 
dcc_free_hostdef(struct dcc_hostdef * host)708 int dcc_free_hostdef(struct dcc_hostdef *host)
709 {
710     /* ANSI C requires free() to accept NULL */
711 
712     free(host->user);
713     free(host->hostname);
714     free(host->ssh_command);
715     free(host->hostdef_string);
716     memset(host, 0xf1, sizeof *host);
717     free(host);
718 
719     return 0;
720 }
721