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