1 | /* |
---|
2 | * General skeleton for adding options to the access control language. The |
---|
3 | * features offered by this module are documented in the hosts_options(5) |
---|
4 | * manual page (source file: hosts_options.5, "nroff -man" format). |
---|
5 | * |
---|
6 | * Notes and warnings for those who want to add features: |
---|
7 | * |
---|
8 | * In case of errors, abort options processing and deny access. There are too |
---|
9 | * many irreversible side effects to make error recovery feasible. For |
---|
10 | * example, it makes no sense to continue after we have already changed the |
---|
11 | * userid. |
---|
12 | * |
---|
13 | * In case of errors, do not terminate the process: the routines might be |
---|
14 | * called from a long-running daemon that should run forever. Instead, call |
---|
15 | * tcpd_jump() which does a non-local goto back into the hosts_access() |
---|
16 | * routine. |
---|
17 | * |
---|
18 | * In case of severe errors, use clean_exit() instead of directly calling |
---|
19 | * exit(), or the inetd may loop on an UDP request. |
---|
20 | * |
---|
21 | * In verification mode (for example, with the "tcpdmatch" command) the |
---|
22 | * "dry_run" flag is set. In this mode, an option function should just "say" |
---|
23 | * what it is going to do instead of really doing it. |
---|
24 | * |
---|
25 | * Some option functions do not return (for example, the twist option passes |
---|
26 | * control to another program). In verification mode (dry_run flag is set) |
---|
27 | * such options should clear the "dry_run" flag to inform the caller of this |
---|
28 | * course of action. |
---|
29 | */ |
---|
30 | |
---|
31 | #ifndef lint |
---|
32 | static char sccsid[] = "@(#) options.c 1.17 96/02/11 17:01:31"; |
---|
33 | #endif |
---|
34 | |
---|
35 | /* System libraries. */ |
---|
36 | |
---|
37 | #include <sys/types.h> |
---|
38 | #include <sys/param.h> |
---|
39 | #include <sys/socket.h> |
---|
40 | #include <sys/stat.h> |
---|
41 | #include <netinet/in.h> |
---|
42 | #include <netdb.h> |
---|
43 | #include <stdio.h> |
---|
44 | #include <syslog.h> |
---|
45 | #include <pwd.h> |
---|
46 | #include <grp.h> |
---|
47 | #include <ctype.h> |
---|
48 | #include <setjmp.h> |
---|
49 | #include <string.h> |
---|
50 | |
---|
51 | #ifndef MAXPATHNAMELEN |
---|
52 | #define MAXPATHNAMELEN BUFSIZ |
---|
53 | #endif |
---|
54 | |
---|
55 | /* Local stuff. */ |
---|
56 | |
---|
57 | #include "tcpd.h" |
---|
58 | |
---|
59 | /* Options runtime support. */ |
---|
60 | |
---|
61 | int dry_run = 0; /* flag set in verification mode */ |
---|
62 | extern jmp_buf tcpd_buf; /* tcpd_jump() support */ |
---|
63 | |
---|
64 | /* Options parser support. */ |
---|
65 | |
---|
66 | static char whitespace_eq[] = "= \t\r\n"; |
---|
67 | #define whitespace (whitespace_eq + 1) |
---|
68 | |
---|
69 | static char *get_field(); /* chew :-delimited field off string */ |
---|
70 | static char *chop_string(); /* strip leading and trailing blanks */ |
---|
71 | |
---|
72 | /* List of functions that implement the options. Add yours here. */ |
---|
73 | |
---|
74 | static void user_option(); /* execute "user name.group" option */ |
---|
75 | static void group_option(); /* execute "group name" option */ |
---|
76 | static void umask_option(); /* execute "umask mask" option */ |
---|
77 | static void linger_option(); /* execute "linger time" option */ |
---|
78 | static void keepalive_option(); /* execute "keepalive" option */ |
---|
79 | static void spawn_option(); /* execute "spawn command" option */ |
---|
80 | static void twist_option(); /* execute "twist command" option */ |
---|
81 | static void rfc931_option(); /* execute "rfc931" option */ |
---|
82 | static void setenv_option(); /* execute "setenv name value" */ |
---|
83 | static void nice_option(); /* execute "nice" option */ |
---|
84 | static void severity_option(); /* execute "severity value" */ |
---|
85 | static void allow_option(); /* execute "allow" option */ |
---|
86 | static void deny_option(); /* execute "deny" option */ |
---|
87 | static void banners_option(); /* execute "banners path" option */ |
---|
88 | |
---|
89 | /* Structure of the options table. */ |
---|
90 | |
---|
91 | struct option { |
---|
92 | char *name; /* keyword name, case is ignored */ |
---|
93 | void (*func) (); /* function that does the real work */ |
---|
94 | int flags; /* see below... */ |
---|
95 | }; |
---|
96 | |
---|
97 | #define NEED_ARG (1<<1) /* option requires argument */ |
---|
98 | #define USE_LAST (1<<2) /* option must be last */ |
---|
99 | #define OPT_ARG (1<<3) /* option has optional argument */ |
---|
100 | #define EXPAND_ARG (1<<4) /* do %x expansion on argument */ |
---|
101 | |
---|
102 | #define need_arg(o) ((o)->flags & NEED_ARG) |
---|
103 | #define opt_arg(o) ((o)->flags & OPT_ARG) |
---|
104 | #define permit_arg(o) ((o)->flags & (NEED_ARG | OPT_ARG)) |
---|
105 | #define use_last(o) ((o)->flags & USE_LAST) |
---|
106 | #define expand_arg(o) ((o)->flags & EXPAND_ARG) |
---|
107 | |
---|
108 | /* List of known keywords. Add yours here. */ |
---|
109 | |
---|
110 | static struct option option_table[] = { |
---|
111 | "user", user_option, NEED_ARG, |
---|
112 | "group", group_option, NEED_ARG, |
---|
113 | "umask", umask_option, NEED_ARG, |
---|
114 | "linger", linger_option, NEED_ARG, |
---|
115 | "keepalive", keepalive_option, 0, |
---|
116 | "spawn", spawn_option, NEED_ARG | EXPAND_ARG, |
---|
117 | "twist", twist_option, NEED_ARG | EXPAND_ARG | USE_LAST, |
---|
118 | "rfc931", rfc931_option, OPT_ARG, |
---|
119 | "setenv", setenv_option, NEED_ARG | EXPAND_ARG, |
---|
120 | "nice", nice_option, OPT_ARG, |
---|
121 | "severity", severity_option, NEED_ARG, |
---|
122 | "allow", allow_option, USE_LAST, |
---|
123 | "deny", deny_option, USE_LAST, |
---|
124 | "banners", banners_option, NEED_ARG, |
---|
125 | 0, |
---|
126 | }; |
---|
127 | |
---|
128 | /* process_options - process access control options */ |
---|
129 | |
---|
130 | void process_options(options, request) |
---|
131 | char *options; |
---|
132 | struct request_info *request; |
---|
133 | { |
---|
134 | char *key; |
---|
135 | char *value; |
---|
136 | char *curr_opt; |
---|
137 | char *next_opt; |
---|
138 | struct option *op; |
---|
139 | char bf[BUFSIZ]; |
---|
140 | |
---|
141 | for (curr_opt = get_field(options); curr_opt; curr_opt = next_opt) { |
---|
142 | next_opt = get_field((char *) 0); |
---|
143 | |
---|
144 | /* |
---|
145 | * Separate the option into name and value parts. For backwards |
---|
146 | * compatibility we ignore exactly one '=' between name and value. |
---|
147 | */ |
---|
148 | curr_opt = chop_string(curr_opt); |
---|
149 | if (*(value = curr_opt + strcspn(curr_opt, whitespace_eq))) { |
---|
150 | if (*value != '=') { |
---|
151 | *value++ = 0; |
---|
152 | value += strspn(value, whitespace); |
---|
153 | } |
---|
154 | if (*value == '=') { |
---|
155 | *value++ = 0; |
---|
156 | value += strspn(value, whitespace); |
---|
157 | } |
---|
158 | } |
---|
159 | if (*value == 0) |
---|
160 | value = 0; |
---|
161 | key = curr_opt; |
---|
162 | |
---|
163 | /* |
---|
164 | * Disallow missing option names (and empty option fields). |
---|
165 | */ |
---|
166 | if (*key == 0) |
---|
167 | tcpd_jump("missing option name"); |
---|
168 | |
---|
169 | /* |
---|
170 | * Lookup the option-specific info and do some common error checks. |
---|
171 | * Delegate option-specific processing to the specific functions. |
---|
172 | */ |
---|
173 | |
---|
174 | for (op = option_table; op->name && STR_NE(op->name, key); op++) |
---|
175 | /* VOID */ ; |
---|
176 | if (op->name == 0) |
---|
177 | tcpd_jump("bad option name: \"%s\"", key); |
---|
178 | if (!value && need_arg(op)) |
---|
179 | tcpd_jump("option \"%s\" requires value", key); |
---|
180 | if (value && !permit_arg(op)) |
---|
181 | tcpd_jump("option \"%s\" requires no value", key); |
---|
182 | if (next_opt && use_last(op)) |
---|
183 | tcpd_jump("option \"%s\" must be at end", key); |
---|
184 | if (value && expand_arg(op)) |
---|
185 | value = chop_string(percent_x(bf, sizeof(bf), value, request)); |
---|
186 | if (hosts_access_verbose) |
---|
187 | syslog(LOG_DEBUG, "option: %s %s", key, value ? value : ""); |
---|
188 | (*(op->func)) (value, request); |
---|
189 | } |
---|
190 | } |
---|
191 | |
---|
192 | /* allow_option - grant access */ |
---|
193 | |
---|
194 | /* ARGSUSED */ |
---|
195 | |
---|
196 | static void allow_option(value, request) |
---|
197 | char *value; |
---|
198 | struct request_info *request; |
---|
199 | { |
---|
200 | longjmp(tcpd_buf, AC_PERMIT); |
---|
201 | } |
---|
202 | |
---|
203 | /* deny_option - deny access */ |
---|
204 | |
---|
205 | /* ARGSUSED */ |
---|
206 | |
---|
207 | static void deny_option(value, request) |
---|
208 | char *value; |
---|
209 | struct request_info *request; |
---|
210 | { |
---|
211 | longjmp(tcpd_buf, AC_DENY); |
---|
212 | } |
---|
213 | |
---|
214 | /* banners_option - expand %<char>, terminate each line with CRLF */ |
---|
215 | |
---|
216 | static void banners_option(value, request) |
---|
217 | char *value; |
---|
218 | struct request_info *request; |
---|
219 | { |
---|
220 | char path[MAXPATHNAMELEN]; |
---|
221 | char ibuf[BUFSIZ]; |
---|
222 | char obuf[2 * BUFSIZ]; |
---|
223 | struct stat st; |
---|
224 | int ch; |
---|
225 | FILE *fp; |
---|
226 | |
---|
227 | sprintf(path, "%s/%s", value, eval_daemon(request)); |
---|
228 | if ((fp = fopen(path, "r")) != 0) { |
---|
229 | while ((ch = fgetc(fp)) == 0) |
---|
230 | write(request->fd, "", 1); |
---|
231 | ungetc(ch, fp); |
---|
232 | while (fgets(ibuf, sizeof(ibuf) - 1, fp)) { |
---|
233 | if (split_at(ibuf, '\n')) |
---|
234 | strcat(ibuf, "\r\n"); |
---|
235 | percent_x(obuf, sizeof(obuf), ibuf, request); |
---|
236 | write(request->fd, obuf, strlen(obuf)); |
---|
237 | } |
---|
238 | fclose(fp); |
---|
239 | } else if (stat(value, &st) < 0) { |
---|
240 | tcpd_warn("%s: %m", value); |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | /* group_option - switch group id */ |
---|
245 | |
---|
246 | /* ARGSUSED */ |
---|
247 | |
---|
248 | static void group_option(value, request) |
---|
249 | char *value; |
---|
250 | struct request_info *request; |
---|
251 | { |
---|
252 | struct group *grp; |
---|
253 | struct group *getgrnam(); |
---|
254 | |
---|
255 | if ((grp = getgrnam(value)) == 0) |
---|
256 | tcpd_jump("unknown group: \"%s\"", value); |
---|
257 | endgrent(); |
---|
258 | |
---|
259 | if (dry_run == 0 && setgid(grp->gr_gid)) |
---|
260 | tcpd_jump("setgid(%s): %m", value); |
---|
261 | } |
---|
262 | |
---|
263 | /* user_option - switch user id */ |
---|
264 | |
---|
265 | /* ARGSUSED */ |
---|
266 | |
---|
267 | static void user_option(value, request) |
---|
268 | char *value; |
---|
269 | struct request_info *request; |
---|
270 | { |
---|
271 | struct passwd *pwd; |
---|
272 | struct passwd *getpwnam(); |
---|
273 | char *group; |
---|
274 | |
---|
275 | if ((group = split_at(value, '.')) != 0) |
---|
276 | group_option(group, request); |
---|
277 | if ((pwd = getpwnam(value)) == 0) |
---|
278 | tcpd_jump("unknown user: \"%s\"", value); |
---|
279 | endpwent(); |
---|
280 | |
---|
281 | if (dry_run == 0 && setuid(pwd->pw_uid)) |
---|
282 | tcpd_jump("setuid(%s): %m", value); |
---|
283 | } |
---|
284 | |
---|
285 | /* umask_option - set file creation mask */ |
---|
286 | |
---|
287 | /* ARGSUSED */ |
---|
288 | |
---|
289 | static void umask_option(value, request) |
---|
290 | char *value; |
---|
291 | struct request_info *request; |
---|
292 | { |
---|
293 | unsigned mask; |
---|
294 | char junk; |
---|
295 | |
---|
296 | if (sscanf(value, "%o%c", &mask, &junk) != 1 || (mask & 0777) != mask) |
---|
297 | tcpd_jump("bad umask value: \"%s\"", value); |
---|
298 | (void) umask(mask); |
---|
299 | } |
---|
300 | |
---|
301 | /* spawn_option - spawn a shell command and wait */ |
---|
302 | |
---|
303 | /* ARGSUSED */ |
---|
304 | |
---|
305 | static void spawn_option(value, request) |
---|
306 | char *value; |
---|
307 | struct request_info *request; |
---|
308 | { |
---|
309 | if (dry_run == 0) |
---|
310 | shell_cmd(value); |
---|
311 | } |
---|
312 | |
---|
313 | /* linger_option - set the socket linger time (Marc Boucher <marc@cam.org>) */ |
---|
314 | |
---|
315 | /* ARGSUSED */ |
---|
316 | |
---|
317 | static void linger_option(value, request) |
---|
318 | char *value; |
---|
319 | struct request_info *request; |
---|
320 | { |
---|
321 | struct linger linger; |
---|
322 | char junk; |
---|
323 | |
---|
324 | if (sscanf(value, "%d%c", &linger.l_linger, &junk) != 1 |
---|
325 | || linger.l_linger < 0) |
---|
326 | tcpd_jump("bad linger value: \"%s\"", value); |
---|
327 | if (dry_run == 0) { |
---|
328 | linger.l_onoff = (linger.l_linger != 0); |
---|
329 | if (setsockopt(request->fd, SOL_SOCKET, SO_LINGER, (char *) &linger, |
---|
330 | sizeof(linger)) < 0) |
---|
331 | tcpd_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); |
---|
332 | } |
---|
333 | } |
---|
334 | |
---|
335 | /* keepalive_option - set the socket keepalive option */ |
---|
336 | |
---|
337 | /* ARGSUSED */ |
---|
338 | |
---|
339 | static void keepalive_option(value, request) |
---|
340 | char *value; |
---|
341 | struct request_info *request; |
---|
342 | { |
---|
343 | static int on = 1; |
---|
344 | |
---|
345 | if (dry_run == 0 && setsockopt(request->fd, SOL_SOCKET, SO_KEEPALIVE, |
---|
346 | (char *) &on, sizeof(on)) < 0) |
---|
347 | tcpd_warn("setsockopt SO_KEEPALIVE: %m"); |
---|
348 | } |
---|
349 | |
---|
350 | /* nice_option - set nice value */ |
---|
351 | |
---|
352 | /* ARGSUSED */ |
---|
353 | |
---|
354 | static void nice_option(value, request) |
---|
355 | char *value; |
---|
356 | struct request_info *request; |
---|
357 | { |
---|
358 | int niceval = 10; |
---|
359 | char junk; |
---|
360 | |
---|
361 | if (value != 0 && sscanf(value, "%d%c", &niceval, &junk) != 1) |
---|
362 | tcpd_jump("bad nice value: \"%s\"", value); |
---|
363 | if (dry_run == 0 && nice(niceval) < 0) |
---|
364 | tcpd_warn("nice(%d): %m", niceval); |
---|
365 | } |
---|
366 | |
---|
367 | /* twist_option - replace process by shell command */ |
---|
368 | |
---|
369 | static void twist_option(value, request) |
---|
370 | char *value; |
---|
371 | struct request_info *request; |
---|
372 | { |
---|
373 | char *error; |
---|
374 | |
---|
375 | if (dry_run != 0) { |
---|
376 | dry_run = 0; |
---|
377 | } else { |
---|
378 | if (resident > 0) |
---|
379 | tcpd_jump("twist option in resident process"); |
---|
380 | |
---|
381 | syslog(deny_severity, "twist %s to %s", eval_client(request), value); |
---|
382 | |
---|
383 | /* Before switching to the shell, set up stdin, stdout and stderr. */ |
---|
384 | |
---|
385 | #define maybe_dup2(from, to) ((from == to) ? to : (close(to), dup(from))) |
---|
386 | |
---|
387 | if (maybe_dup2(request->fd, 0) != 0 || |
---|
388 | maybe_dup2(request->fd, 1) != 1 || |
---|
389 | maybe_dup2(request->fd, 2) != 2) { |
---|
390 | error = "twist_option: dup: %m"; |
---|
391 | } else { |
---|
392 | if (request->fd > 2) |
---|
393 | close(request->fd); |
---|
394 | (void) execl("/bin/sh", "sh", "-c", value, (char *) 0); |
---|
395 | error = "twist_option: /bin/sh: %m"; |
---|
396 | } |
---|
397 | |
---|
398 | /* Something went wrong: we MUST terminate the process. */ |
---|
399 | |
---|
400 | tcpd_warn(error); |
---|
401 | clean_exit(request); |
---|
402 | } |
---|
403 | } |
---|
404 | |
---|
405 | /* rfc931_option - look up remote user name */ |
---|
406 | |
---|
407 | static void rfc931_option(value, request) |
---|
408 | char *value; |
---|
409 | struct request_info *request; |
---|
410 | { |
---|
411 | int timeout; |
---|
412 | char junk; |
---|
413 | |
---|
414 | if (value != 0) { |
---|
415 | if (sscanf(value, "%d%c", &timeout, &junk) != 1 || timeout <= 0) |
---|
416 | tcpd_jump("bad rfc931 timeout: \"%s\"", value); |
---|
417 | rfc931_timeout = timeout; |
---|
418 | } |
---|
419 | (void) eval_user(request); |
---|
420 | } |
---|
421 | |
---|
422 | /* setenv_option - set environment variable */ |
---|
423 | |
---|
424 | /* ARGSUSED */ |
---|
425 | |
---|
426 | static void setenv_option(value, request) |
---|
427 | char *value; |
---|
428 | struct request_info *request; |
---|
429 | { |
---|
430 | char *var_value; |
---|
431 | |
---|
432 | if (*(var_value = value + strcspn(value, whitespace))) |
---|
433 | *var_value++ = 0; |
---|
434 | if (setenv(chop_string(value), chop_string(var_value), 1)) |
---|
435 | tcpd_jump("memory allocation failure"); |
---|
436 | } |
---|
437 | |
---|
438 | /* |
---|
439 | * The severity option goes last because it comes with a huge amount of ugly |
---|
440 | * #ifdefs and tables. |
---|
441 | */ |
---|
442 | |
---|
443 | struct syslog_names { |
---|
444 | char *name; |
---|
445 | int value; |
---|
446 | }; |
---|
447 | |
---|
448 | static struct syslog_names log_fac[] = { |
---|
449 | #ifdef LOG_KERN |
---|
450 | "kern", LOG_KERN, |
---|
451 | #endif |
---|
452 | #ifdef LOG_USER |
---|
453 | "user", LOG_USER, |
---|
454 | #endif |
---|
455 | #ifdef LOG_MAIL |
---|
456 | "mail", LOG_MAIL, |
---|
457 | #endif |
---|
458 | #ifdef LOG_DAEMON |
---|
459 | "daemon", LOG_DAEMON, |
---|
460 | #endif |
---|
461 | #ifdef LOG_AUTH |
---|
462 | "auth", LOG_AUTH, |
---|
463 | #endif |
---|
464 | #ifdef LOG_LPR |
---|
465 | "lpr", LOG_LPR, |
---|
466 | #endif |
---|
467 | #ifdef LOG_NEWS |
---|
468 | "news", LOG_NEWS, |
---|
469 | #endif |
---|
470 | #ifdef LOG_UUCP |
---|
471 | "uucp", LOG_UUCP, |
---|
472 | #endif |
---|
473 | #ifdef LOG_CRON |
---|
474 | "cron", LOG_CRON, |
---|
475 | #endif |
---|
476 | #ifdef LOG_LOCAL0 |
---|
477 | "local0", LOG_LOCAL0, |
---|
478 | #endif |
---|
479 | #ifdef LOG_LOCAL1 |
---|
480 | "local1", LOG_LOCAL1, |
---|
481 | #endif |
---|
482 | #ifdef LOG_LOCAL2 |
---|
483 | "local2", LOG_LOCAL2, |
---|
484 | #endif |
---|
485 | #ifdef LOG_LOCAL3 |
---|
486 | "local3", LOG_LOCAL3, |
---|
487 | #endif |
---|
488 | #ifdef LOG_LOCAL4 |
---|
489 | "local4", LOG_LOCAL4, |
---|
490 | #endif |
---|
491 | #ifdef LOG_LOCAL5 |
---|
492 | "local5", LOG_LOCAL5, |
---|
493 | #endif |
---|
494 | #ifdef LOG_LOCAL6 |
---|
495 | "local6", LOG_LOCAL6, |
---|
496 | #endif |
---|
497 | #ifdef LOG_LOCAL7 |
---|
498 | "local7", LOG_LOCAL7, |
---|
499 | #endif |
---|
500 | 0, |
---|
501 | }; |
---|
502 | |
---|
503 | static struct syslog_names log_sev[] = { |
---|
504 | #ifdef LOG_EMERG |
---|
505 | "emerg", LOG_EMERG, |
---|
506 | #endif |
---|
507 | #ifdef LOG_ALERT |
---|
508 | "alert", LOG_ALERT, |
---|
509 | #endif |
---|
510 | #ifdef LOG_CRIT |
---|
511 | "crit", LOG_CRIT, |
---|
512 | #endif |
---|
513 | #ifdef LOG_ERR |
---|
514 | "err", LOG_ERR, |
---|
515 | #endif |
---|
516 | #ifdef LOG_WARNING |
---|
517 | "warning", LOG_WARNING, |
---|
518 | #endif |
---|
519 | #ifdef LOG_NOTICE |
---|
520 | "notice", LOG_NOTICE, |
---|
521 | #endif |
---|
522 | #ifdef LOG_INFO |
---|
523 | "info", LOG_INFO, |
---|
524 | #endif |
---|
525 | #ifdef LOG_DEBUG |
---|
526 | "debug", LOG_DEBUG, |
---|
527 | #endif |
---|
528 | 0, |
---|
529 | }; |
---|
530 | |
---|
531 | /* severity_map - lookup facility or severity value */ |
---|
532 | |
---|
533 | static int severity_map(table, name) |
---|
534 | struct syslog_names *table; |
---|
535 | char *name; |
---|
536 | { |
---|
537 | struct syslog_names *t; |
---|
538 | |
---|
539 | for (t = table; t->name; t++) |
---|
540 | if (STR_EQ(t->name, name)) |
---|
541 | return (t->value); |
---|
542 | tcpd_jump("bad syslog facility or severity: \"%s\"", name); |
---|
543 | /* NOTREACHED */ |
---|
544 | } |
---|
545 | |
---|
546 | /* severity_option - change logging severity for this event (Dave Mitchell) */ |
---|
547 | |
---|
548 | /* ARGSUSED */ |
---|
549 | |
---|
550 | static void severity_option(value, request) |
---|
551 | char *value; |
---|
552 | struct request_info *request; |
---|
553 | { |
---|
554 | char *level = split_at(value, '.'); |
---|
555 | |
---|
556 | allow_severity = deny_severity = level ? |
---|
557 | severity_map(log_fac, value) | severity_map(log_sev, level) : |
---|
558 | severity_map(log_sev, value); |
---|
559 | } |
---|
560 | |
---|
561 | /* get_field - return pointer to next field in string */ |
---|
562 | |
---|
563 | static char *get_field(string) |
---|
564 | char *string; |
---|
565 | { |
---|
566 | static char *last = ""; |
---|
567 | char *src; |
---|
568 | char *dst; |
---|
569 | char *ret; |
---|
570 | int ch; |
---|
571 | |
---|
572 | /* |
---|
573 | * This function returns pointers to successive fields within a given |
---|
574 | * string. ":" is the field separator; warn if the rule ends in one. It |
---|
575 | * replaces a "\:" sequence by ":", without treating the result of |
---|
576 | * substitution as field terminator. A null argument means resume search |
---|
577 | * where the previous call terminated. This function destroys its |
---|
578 | * argument. |
---|
579 | * |
---|
580 | * Work from explicit source or from memory. While processing \: we |
---|
581 | * overwrite the input. This way we do not have to maintain buffers for |
---|
582 | * copies of input fields. |
---|
583 | */ |
---|
584 | |
---|
585 | src = dst = ret = (string ? string : last); |
---|
586 | if (src[0] == 0) |
---|
587 | return (0); |
---|
588 | |
---|
589 | while (ch = *src) { |
---|
590 | if (ch == ':') { |
---|
591 | if (*++src == 0) |
---|
592 | tcpd_warn("rule ends in \":\""); |
---|
593 | break; |
---|
594 | } |
---|
595 | if (ch == '\\' && src[1] == ':') |
---|
596 | src++; |
---|
597 | *dst++ = *src++; |
---|
598 | } |
---|
599 | last = src; |
---|
600 | *dst = 0; |
---|
601 | return (ret); |
---|
602 | } |
---|
603 | |
---|
604 | /* chop_string - strip leading and trailing blanks from string */ |
---|
605 | |
---|
606 | static char *chop_string(string) |
---|
607 | register char *string; |
---|
608 | { |
---|
609 | char *start = 0; |
---|
610 | char *end; |
---|
611 | char *cp; |
---|
612 | |
---|
613 | for (cp = string; *cp; cp++) { |
---|
614 | if (!isspace(*cp)) { |
---|
615 | if (start == 0) |
---|
616 | start = cp; |
---|
617 | end = cp; |
---|
618 | } |
---|
619 | } |
---|
620 | return (start ? (end[1] = 0, start) : cp); |
---|
621 | } |
---|