xref: /netbsd-src/external/ibm-public/postfix/dist/src/master/master_ent.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: master_ent.c,v 1.3 2020/03/18 19:05:16 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	master_ent 3
6 /* SUMMARY
7 /*	Postfix master - config file access
8 /* SYNOPSIS
9 /*	#include "master.h"
10 /*
11 /*	void	fset_master_ent(path)
12 /*	char	*path;
13 /*
14 /*	void	set_master_ent()
15 /*
16 /*	MASTER_SERV *get_master_ent()
17 /*
18 /*	void	end_master_ent()
19 /*
20 /*	void	print_master_ent(entry)
21 /*	MASTER_SERV *entry;
22 /*
23 /*	void	free_master_ent(entry)
24 /*	MASTER_SERV *entry;
25 /* DESCRIPTION
26 /*	This module implements a simple programmatic interface
27 /*	for accessing Postfix master process configuration files.
28 /*
29 /*	fset_master_ent() specifies the location of the master process
30 /*	configuration file.  The pathname is copied.
31 /*
32 /*	set_master_ent() opens the configuration file. It is an error
33 /*	to call this routine while the configuration file is still open.
34 /*	It is an error to open a configuration file without specifying
35 /*	its name to fset_master_ent().
36 /*
37 /*	get_master_ent() reads the next entry from an open configuration
38 /*	file and returns the parsed result. A null result means the end
39 /*	of file was reached.
40 /*
41 /*	print_master_ent() prints the specified service entry.
42 /*
43 /*	end_master_ent() closes an open configuration file. It is an error
44 /*	to call this routine when the configuration file is not open.
45 /*
46 /*	free_master_ent() destroys the memory used for a parsed configuration
47 /*	file entry.
48 /* DIAGNOSTICS
49 /*	Panics: interface violations. Fatal errors: memory allocation
50 /*	failure.
51 /* BUGS
52 /* SEE ALSO
53 /* LICENSE
54 /* .ad
55 /* .fi
56 /*	The Secure Mailer license must be distributed with this software.
57 /* AUTHOR(S)
58 /*	Wietse Venema
59 /*	IBM T.J. Watson Research
60 /*	P.O. Box 704
61 /*	Yorktown Heights, NY 10598, USA
62 /*
63 /*	Wietse Venema
64 /*	Google, Inc.
65 /*	111 8th Avenue
66 /*	New York, NY 10011, USA
67 /*--*/
68 
69 /* System libraries. */
70 
71 #include <sys_defs.h>
72 #include <netinet/in.h>
73 #include <stdarg.h>
74 #include <string.h>
75 #include <stdlib.h>
76 #include <unistd.h>
77 #include <ctype.h>
78 #include <fcntl.h>
79 
80 #ifdef STRCASECMP_IN_STRINGS_H
81 #include <strings.h>
82 #endif
83 
84 /* Utility libraries. */
85 
86 #include <msg.h>
87 #include <mymalloc.h>
88 #include <vstring.h>
89 #include <vstream.h>
90 #include <argv.h>
91 #include <stringops.h>
92 #include <readlline.h>
93 #include <inet_addr_list.h>
94 #include <host_port.h>
95 #include <inet_addr_host.h>
96 #include <sock_addr.h>
97 #include <inet_proto.h>
98 
99 /* Global library. */
100 
101 #include <match_service.h>
102 #include <mail_proto.h>
103 #include <mail_params.h>
104 #include <own_inet_addr.h>
105 #include <wildcard_inet_addr.h>
106 #include <mail_conf.h>
107 
108 /* Local stuff. */
109 
110 #include "master_proto.h"
111 #include "master.h"
112 
113 static char *master_path;		/* config file name */
114 static VSTREAM *master_fp;		/* config file pointer */
115 static int master_line_last;		/* config file line number */
116 static int master_line;			/* config file line number */
117 static ARGV *master_disable;		/* disabled service patterns */
118 
119 static char master_blanks[] = CHARS_SPACE;	/* field delimiters */
120 
121 /* fset_master_ent - specify configuration file pathname */
122 
123 void    fset_master_ent(char *path)
124 {
125     if (master_path != 0)
126 	myfree(master_path);
127     master_path = mystrdup(path);
128 }
129 
130 /* set_master_ent - open configuration file */
131 
132 void    set_master_ent()
133 {
134     const char *myname = "set_master_ent";
135     char   *disable;
136 
137     if (master_fp != 0)
138 	msg_panic("%s: configuration file still open", myname);
139     if (master_path == 0)
140 	msg_panic("%s: no configuration file specified", myname);
141     if ((master_fp = vstream_fopen(master_path, O_RDONLY, 0)) == 0)
142 	msg_fatal("open %s: %m", master_path);
143     master_line_last = 0;
144     if (master_disable != 0)
145 	msg_panic("%s: service disable list still exists", myname);
146     if (inet_proto_info()->ai_family_list[0] == 0) {
147 	msg_warn("all network protocols are disabled (%s = %s)",
148 		 VAR_INET_PROTOCOLS, var_inet_protocols);
149 	msg_warn("disabling all type \"inet\" services in master.cf");
150 	disable = concatenate(MASTER_XPORT_NAME_INET, ",",
151 			      var_master_disable, (char *) 0);
152 	master_disable = match_service_init(disable);
153 	myfree(disable);
154     } else
155 	master_disable = match_service_init(var_master_disable);
156 }
157 
158 /* end_master_ent - close configuration file */
159 
160 void    end_master_ent()
161 {
162     const char *myname = "end_master_ent";
163 
164     if (master_fp == 0)
165 	msg_panic("%s: configuration file not open", myname);
166     if (vstream_fclose(master_fp) != 0)
167 	msg_fatal("%s: close configuration file: %m", myname);
168     master_fp = 0;
169     if (master_disable == 0)
170 	msg_panic("%s: no service disable list", myname);
171     match_service_free(master_disable);
172     master_disable = 0;
173 }
174 
175 /* master_conf_context - plot the target range */
176 
177 static const char *master_conf_context(void)
178 {
179     static VSTRING *context_buf = 0;
180 
181     if (context_buf == 0)
182 	context_buf = vstring_alloc(100);
183     vstring_sprintf(context_buf, "%s: line %d", master_path, master_line);
184     return (vstring_str(context_buf));
185 }
186 
187 /* fatal_with_context - print fatal error with file/line context */
188 
189 static NORETURN PRINTFLIKE(1, 2) fatal_with_context(char *format,...)
190 {
191     const char *myname = "fatal_with_context";
192     VSTRING *vp = vstring_alloc(100);
193     va_list ap;
194 
195     if (master_path == 0)
196 	msg_panic("%s: no configuration file specified", myname);
197 
198     va_start(ap, format);
199     vstring_vsprintf(vp, format, ap);
200     va_end(ap);
201     msg_fatal("%s: %s", master_conf_context(), vstring_str(vp));
202 }
203 
204 /* fatal_invalid_field - report invalid field value */
205 
206 static NORETURN fatal_invalid_field(char *name, char *value)
207 {
208     fatal_with_context("field \"%s\": bad value: \"%s\"", name, value);
209 }
210 
211 /* get_str_ent - extract string field */
212 
213 static char *get_str_ent(char **bufp, char *name, char *def_val)
214 {
215     char   *value;
216 
217     if ((value = mystrtok(bufp, master_blanks)) == 0)
218 	fatal_with_context("missing \"%s\" field", name);
219     if (strcmp(value, "-") == 0) {
220 	if (def_val == 0)
221 	    fatal_with_context("field \"%s\" has no default value", name);
222 	if (warn_compat_break_chroot && strcmp(name, "chroot") == 0)
223 	    msg_info("%s: using backwards-compatible default setting "
224 		     "%s=%s", master_conf_context(), name, def_val);
225 	return (def_val);
226     } else {
227 	return (value);
228     }
229 }
230 
231 /* get_bool_ent - extract boolean field */
232 
233 static int get_bool_ent(char **bufp, char *name, char *def_val)
234 {
235     char   *value;
236 
237     value = get_str_ent(bufp, name, def_val);
238     if (strcmp("y", value) == 0) {
239 	return (1);
240     } else if (strcmp("n", value) == 0) {
241 	return (0);
242     } else {
243 	fatal_invalid_field(name, value);
244     }
245     /* NOTREACHED */
246 }
247 
248 /* get_int_ent - extract integer field */
249 
250 static int get_int_ent(char **bufp, char *name, char *def_val, int min_val)
251 {
252     char   *value;
253     int     n;
254 
255     value = get_str_ent(bufp, name, def_val);
256     if (!ISDIGIT(*value) || (n = atoi(value)) < min_val)
257 	fatal_invalid_field(name, value);
258     return (n);
259 }
260 
261 /* get_master_ent - read entry from configuration file */
262 
263 MASTER_SERV *get_master_ent()
264 {
265     VSTRING *buf = vstring_alloc(100);
266     VSTRING *junk = vstring_alloc(100);
267     MASTER_SERV *serv;
268     char   *cp;
269     char   *name;
270     char   *host = 0;
271     char   *port = 0;
272     char   *transport;
273     int     private;
274     int     unprivileged;		/* passed on to child */
275     int     chroot;			/* passed on to child */
276     char   *command;
277     int     n;
278     char   *bufp;
279     char   *atmp;
280     const char *parse_err;
281     static char *saved_interfaces = 0;
282     char   *err;
283 
284     if (master_fp == 0)
285 	msg_panic("get_master_ent: config file not open");
286     if (master_disable == 0)
287 	msg_panic("get_master_ent: no service disable list");
288 
289     /*
290      * XXX We cannot change the inet_interfaces setting for a running master
291      * process. Listening sockets are inherited by child processes so that
292      * closing and reopening those sockets in the master does not work.
293      *
294      * Another problem is that library routines still cache results that are
295      * based on the old inet_interfaces setting. It is too much trouble to
296      * recompute everything.
297      *
298      * In order to keep our data structures consistent we ignore changes in
299      * inet_interfaces settings, and issue a warning instead.
300      */
301     if (saved_interfaces == 0)
302 	saved_interfaces = mystrdup(var_inet_interfaces);
303 
304     /*
305      * Skip blank lines and comment lines.
306      */
307     for (;;) {
308 	if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) {
309 	    vstring_free(buf);
310 	    vstring_free(junk);
311 	    return (0);
312 	}
313 	bufp = vstring_str(buf);
314 	if ((cp = mystrtok(&bufp, master_blanks)) == 0)
315 	    continue;
316 	name = cp;
317 	transport = get_str_ent(&bufp, "transport type", (char *) 0);
318 	vstring_sprintf(junk, "%s/%s", name, transport);
319 	if (match_service_match(master_disable, vstring_str(junk)) == 0)
320 	    break;
321     }
322 
323     /*
324      * Parse one logical line from the configuration file. Initialize service
325      * structure members in order.
326      */
327     serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV));
328     serv->next = 0;
329 
330     /*
331      * Flags member.
332      */
333     serv->flags = 0;
334 
335     /*
336      * All servers busy warning timer.
337      */
338     serv->busy_warn_time = 0;
339 
340     /*
341      * Service name. Syntax is transport-specific.
342      */
343     serv->ext_name = mystrdup(name);
344 
345     /*
346      * Transport type: inet (wild-card listen or virtual) or unix.
347      */
348 #define STR_SAME	!strcmp
349 
350     if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) {
351 	if (!STR_SAME(saved_interfaces, var_inet_interfaces)) {
352 	    msg_warn("service %s: ignoring %s change",
353 		     serv->ext_name, VAR_INET_INTERFACES);
354 	    msg_warn("to change %s, stop and start Postfix",
355 		     VAR_INET_INTERFACES);
356 	}
357 	serv->type = MASTER_SERV_TYPE_INET;
358 	atmp = mystrdup(name);
359 	if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0)
360 	    fatal_with_context("%s in \"%s\"", parse_err, name);
361 	if (*host) {
362 	    serv->flags |= MASTER_FLAG_INETHOST;/* host:port */
363 	    MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *)
364 		mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv)));
365 	    inet_addr_list_init(MASTER_INET_ADDRLIST(serv));
366 	    if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0)
367 		fatal_with_context("bad hostname or network address: %s", name);
368 	    inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv));
369 	    serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used;
370 	} else {
371 	    MASTER_INET_ADDRLIST(serv) =
372 		strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ?
373 		own_inet_addr_list() :		/* virtual */
374 		wildcard_inet_addr_list();	/* wild-card */
375 	    inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv));
376 	    serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used;
377 	}
378 	MASTER_INET_PORT(serv) = mystrdup(port);
379 	for (n = 0; /* see below */ ; n++) {
380 	    if (n >= MASTER_INET_ADDRLIST(serv)->used) {
381 		serv->flags |= MASTER_FLAG_LOCAL_ONLY;
382 		break;
383 	    }
384 	    if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n)))
385 		break;
386 	}
387     } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) {
388 	serv->type = MASTER_SERV_TYPE_UNIX;
389 	serv->listen_fd_count = 1;
390 	serv->flags |= MASTER_FLAG_LOCAL_ONLY;
391     } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) {
392 	serv->type = MASTER_SERV_TYPE_UXDG;
393 	serv->listen_fd_count = 1;
394 	serv->flags |= MASTER_FLAG_LOCAL_ONLY;
395     } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) {
396 	serv->type = MASTER_SERV_TYPE_FIFO;
397 	serv->listen_fd_count = 1;
398 	serv->flags |= MASTER_FLAG_LOCAL_ONLY;
399 #ifdef MASTER_SERV_TYPE_PASS
400     } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) {
401 	serv->type = MASTER_SERV_TYPE_PASS;
402 	serv->listen_fd_count = 1;
403 	/* If this is a connection screener, remote clients are likely. */
404 #endif
405     } else {
406 	fatal_with_context("bad transport type: %s", transport);
407     }
408 
409     /*
410      * Service class: public or private.
411      */
412     private = get_bool_ent(&bufp, "private", "y");
413 
414     /*
415      * Derive an internal service name. The name may depend on service
416      * attributes such as privacy.
417      */
418     if (serv->type == MASTER_SERV_TYPE_INET) {
419 	MAI_HOSTADDR_STR host_addr;
420 	MAI_SERVPORT_STR serv_port;
421 	struct addrinfo *res0;
422 
423 	if (private)
424 	    fatal_with_context("inet service cannot be private");
425 
426 	/*
427 	 * Canonicalize endpoint names so that we correctly handle "reload"
428 	 * requests after someone changes "25" into "smtp" or vice versa.
429 	 */
430 	if (*host == 0)
431 	    host = 0;
432 	/* Canonicalize numeric host and numeric or symbolic service. */
433 	if (hostaddr_to_sockaddr(host, port, 0, &res0) == 0) {
434 	    SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen,
435 				 host ? &host_addr : (MAI_HOSTADDR_STR *) 0,
436 				 &serv_port, 0);
437 	    serv->name = (host ? concatenate("[", host_addr.buf, "]:",
438 					     serv_port.buf, (char *) 0) :
439 			  mystrdup(serv_port.buf));
440 	    freeaddrinfo(res0);
441 	}
442 	/* Canonicalize numeric or symbolic service. */
443 	else if (hostaddr_to_sockaddr((char *) 0, port, 0, &res0) == 0) {
444 	    SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen,
445 				 (MAI_HOSTADDR_STR *) 0, &serv_port, 0);
446 	    serv->name = (host ? concatenate("[", host, "]:",
447 					     serv_port.buf, (char *) 0) :
448 			  mystrdup(serv_port.buf));
449 	    freeaddrinfo(res0);
450 	}
451 	/* Bad service name? */
452 	else
453 	    serv->name = mystrdup(name);
454 	myfree(atmp);
455     } else if (serv->type == MASTER_SERV_TYPE_UNIX) {
456 	serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
457 				   MAIL_CLASS_PUBLIC, name);
458     } else if (serv->type == MASTER_SERV_TYPE_UXDG) {
459 	serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
460 				   MAIL_CLASS_PUBLIC, name);
461     } else if (serv->type == MASTER_SERV_TYPE_FIFO) {
462 	serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
463 				   MAIL_CLASS_PUBLIC, name);
464 #ifdef MASTER_SERV_TYPE_PASS
465     } else if (serv->type == MASTER_SERV_TYPE_PASS) {
466 	serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE :
467 				   MAIL_CLASS_PUBLIC, name);
468 #endif
469     } else {
470 	msg_panic("bad transport type: %d", serv->type);
471     }
472 
473     /*
474      * Listen socket(s). XXX We pre-allocate storage because the number of
475      * sockets is frozen anyway once we build the command-line vector below.
476      */
477     if (serv->listen_fd_count == 0) {
478 	fatal_with_context("no valid IP address found: %s", name);
479     }
480     serv->listen_fd = (int *) mymalloc(sizeof(int) * serv->listen_fd_count);
481     for (n = 0; n < serv->listen_fd_count; n++)
482 	serv->listen_fd[n] = -1;
483 
484     /*
485      * Privilege level. Default is to restrict process privileges to those of
486      * the mail owner.
487      */
488     unprivileged = get_bool_ent(&bufp, "unprivileged", "y");
489 
490     /*
491      * Chroot. Default is to restrict file system access to the mail queue.
492      * XXX Chroot cannot imply unprivileged service (for example, the pickup
493      * service runs chrooted but needs privileges to open files as the user).
494      */
495     chroot = get_bool_ent(&bufp, "chroot", var_compat_level < 1 ? "y" : "n");
496 
497     /*
498      * Wakeup timer. XXX should we require that var_proc_limit == 1? Right
499      * now, the only services that have a wakeup timer also happen to be the
500      * services that have at most one running instance: local pickup and
501      * local delivery.
502      */
503     serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0);
504 
505     /*
506      * Find out if the wakeup time is conditional, i.e., wakeup triggers
507      * should not be sent until the service has actually been used.
508      */
509     if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?')
510 	serv->flags |= MASTER_FLAG_CONDWAKE;
511 
512     /*
513      * Concurrency limit. Zero means no limit.
514      */
515     vstring_sprintf(junk, "%d", var_proc_limit);
516     serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0);
517 
518     /*
519      * Path to command,
520      */
521     command = get_str_ent(&bufp, "command", (char *) 0);
522     serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0);
523 
524     /*
525      * Idle and total process count.
526      */
527     serv->avail_proc = 0;
528     serv->total_proc = 0;
529 
530     /*
531      * Backoff time in case a service is broken.
532      */
533     serv->throttle_delay = var_throttle_time;
534 
535     /*
536      * Shared channel for child status updates.
537      */
538     serv->status_fd[0] = serv->status_fd[1] = -1;
539 
540     /*
541      * Child process structures.
542      */
543     serv->children = 0;
544 
545     /*
546      * Command-line vector. Add "-n service_name" when the process name
547      * basename differs from the service name. Always add the transport.
548      */
549     serv->args = argv_alloc(0);
550     argv_add(serv->args, command, (char *) 0);
551     if (serv->max_proc == 1)
552 	argv_add(serv->args, "-l", (char *) 0);
553     if (serv->max_proc == 0)
554 	argv_add(serv->args, "-z", (char *) 0);
555     if (strcmp(basename(command), name) != 0)
556 	argv_add(serv->args, "-n", name, (char *) 0);
557     argv_add(serv->args, "-t", transport, (char *) 0);
558     if (master_detach == 0)
559 	argv_add(serv->args, "-d", (char *) 0);
560     if (msg_verbose)
561 	argv_add(serv->args, "-v", (char *) 0);
562     if (unprivileged)
563 	argv_add(serv->args, "-u", (char *) 0);
564     if (chroot)
565 	argv_add(serv->args, "-c", (char *) 0);
566     if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) {
567 	argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0);
568 	serv->stress_param_val =
569 	    serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1;
570 	serv->stress_param_val[0] = 0;
571     } else
572 	serv->stress_param_val = 0;
573     serv->stress_expire_time = 0;
574     if (serv->listen_fd_count > 1)
575 	argv_add(serv->args, "-s",
576 	    vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)),
577 		 (char *) 0);
578     while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) {
579 	if (*cp == CHARS_BRACE[0]
580 	    && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0)
581 	    fatal_with_context("%s", err);
582 	argv_add(serv->args, cp, (char *) 0);
583     }
584     argv_terminate(serv->args);
585 
586     /*
587      * Cleanup.
588      */
589     vstring_free(buf);
590     vstring_free(junk);
591     return (serv);
592 }
593 
594 /* print_master_ent - show service entry contents */
595 
596 void    print_master_ent(MASTER_SERV *serv)
597 {
598     char  **cpp;
599 
600     msg_info("====start service entry");
601     msg_info("flags: %d", serv->flags);
602     msg_info("name: %s", serv->name);
603     msg_info("type: %s",
604 	     serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX :
605 	     serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO :
606 	     serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET :
607 #ifdef MASTER_SERV_TYPE_PASS
608 	     serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS :
609 #endif
610 	     serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG :
611 	     "unknown transport type");
612     msg_info("listen_fd_count: %d", serv->listen_fd_count);
613     msg_info("wakeup: %d", serv->wakeup_time);
614     msg_info("max_proc: %d", serv->max_proc);
615     msg_info("path: %s", serv->path);
616     for (cpp = serv->args->argv; *cpp; cpp++)
617 	msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp);
618     msg_info("avail_proc: %d", serv->avail_proc);
619     msg_info("total_proc: %d", serv->total_proc);
620     msg_info("throttle_delay: %d", serv->throttle_delay);
621     msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]);
622     msg_info("children: 0x%lx", (long) serv->children);
623     msg_info("next: 0x%lx", (long) serv->next);
624     msg_info("====end service entry");
625 }
626 
627 /* free_master_ent - destroy process entry */
628 
629 void    free_master_ent(MASTER_SERV *serv)
630 {
631 
632     /*
633      * Undo what get_master_ent() created.
634      */
635     if (serv->flags & MASTER_FLAG_INETHOST) {
636 	inet_addr_list_free(MASTER_INET_ADDRLIST(serv));
637 	myfree((void *) MASTER_INET_ADDRLIST(serv));
638     }
639     if (serv->type == MASTER_SERV_TYPE_INET)
640 	myfree(MASTER_INET_PORT(serv));
641     myfree(serv->ext_name);
642     myfree(serv->name);
643     myfree(serv->path);
644     argv_free(serv->args);
645     myfree((void *) serv->listen_fd);
646     myfree((void *) serv);
647 }
648