/*++ /* NAME /* config_known_tcp_ports 3 /* SUMMARY /* parse and store known TCP port configuration /* SYNOPSIS /* #include /* /* void config_known_tcp_ports( /* const char *source, /* const char *settings); /* DESCRIPTION /* config_known_tcp_ports() parses the known TCP port information /* in the settings argument, and reports any warnings to the standard /* error stream. The source argument is used to provide warning /* context. It typically is a configuration parameter name. /* .SH EXPECTED SYNTAX (ABNF) /* configuration = empty | name-to-port *("," name-to-port) /* name-to-port = 1*(name "=") port /* SH EXAMPLES /* In the example below, the whitespace is optional. /* smtp = 25, smtps = submissions = 465, submission = 587 /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* * System library. */ #include /* * Utility library. */ #include #include #include #include #include /* * Application-specific. */ #include /* config_known_tcp_ports - parse configuration and store associations */ void config_known_tcp_ports(const char *source, const char *settings) { ARGV *associations; ARGV *association; char **cpp; clear_known_tcp_ports(); /* * The settings is in the form of associations separated by comma. Split * it into separate associations. */ associations = argv_split(settings, ","); if (associations->argc == 0) { argv_free(associations); return; } /* * Each association is in the form of "1*(name =) port". We use * argv_split() to carve this up, then we use mystrtok() to validate the * individual fragments. But first we prepend and append space so that we * get sensible results when an association starts or ends in "=". */ for (cpp = associations->argv; *cpp != 0; cpp++) { char *temp = concatenate(" ", *cpp, " ", (char *) 0); association = argv_split_at(temp, '='); myfree(temp); if (association->argc == 0) { /* empty, ignore */ ; } else if (association->argc == 1) { msg_warn("%s: in \"%s\" is not in \"name = value\" form", source, *cpp); } else { char *bp; char *lhs; char *rhs; const char *err = 0; int n; bp = association->argv[association->argc - 1]; if ((rhs = mystrtok(&bp, CHARS_SPACE)) == 0) { err = "missing port value after \"=\""; } else if (mystrtok(&bp, CHARS_SPACE) != 0) { err = "whitespace in port number"; } else { for (n = 0; n < association->argc - 1; n++) { const char *new_err; bp = association->argv[n]; if ((lhs = mystrtok(&bp, CHARS_SPACE)) == 0) { new_err = "missing service name before \"=\""; } else if (mystrtok(&bp, CHARS_SPACE) != 0) { new_err = "whitespace in service name"; } else { new_err = add_known_tcp_port(lhs, rhs); } if (new_err != 0 && err == 0) err = new_err; } } if (err != 0) { msg_warn("%s: in \"%s\": %s", source, *cpp, err); } } argv_free(association); } argv_free(associations); } #ifdef TEST #include #include #include #define STR(x) vstring_str(x) /* TODO(wietse) make this a proper VSTREAM interface */ /* vstream_swap - kludge to capture output for testing */ static void vstream_swap(VSTREAM *one, VSTREAM *two) { VSTREAM save; save = *one; *one = *two; *two = save; } struct test_case { const char *label; /* identifies test case */ const char *config; /* configuration under test */ const char *exp_warning; /* expected warning or null */ const char *exp_export; /* expected export or null */ }; static struct test_case test_cases[] = { {"good", /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24", /* warning */ "", /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" }, {"equal-equal", /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24", /* warning */ "config_known_tcp_ports: warning: equal-equal: " "in \" smtps == submissions = 465\": missing service name before " "\"=\"\n", /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" }, {"port test 1", /* config */ "smtps = submission =", /* warning */ "config_known_tcp_ports: warning: port test 1: " "in \"smtps = submission =\": missing port value after \"=\"\n", /* export */ "" }, {"port test 2", /* config */ "smtps = submission = 4 65", /* warning */ "config_known_tcp_ports: warning: port test 2: " "in \"smtps = submission = 4 65\": whitespace in port number\n", /* export */ "" }, {"port test 3", /* config */ "lmtp = 24, smtps = submission = foo", /* warning */ "config_known_tcp_ports: warning: port test 3: " "in \" smtps = submission = foo\": non-numerical service port\n", /* export */ "lmtp=24" }, {"service name test 1", /* config */ "smtps = sub mission = 465", /* warning */ "config_known_tcp_ports: warning: service name test 1: " "in \"smtps = sub mission = 465\": whitespace in service name\n", /* export */ "smtps=465" }, {"service name test 2", /* config */ "lmtp = 24, smtps = 1234 = submissions = 465", /* warning */ "config_known_tcp_ports: warning: service name test 2: " "in \" smtps = 1234 = submissions = 465\": numerical service name\n", /* export */ "lmtp=24 smtps=465 submissions=465" }, 0, }; int main(int argc, char **argv) { VSTRING *export_buf; struct test_case *tp; int pass = 0; int fail = 0; int test_failed; const char *export; VSTRING *msg_buf; VSTREAM *memory_stream; #define STRING_OR_NULL(s) ((s) ? (s) : "(null)") msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR); export_buf = vstring_alloc(100); msg_buf = vstring_alloc(100); for (tp = test_cases; tp->label != 0; tp++) { test_failed = 0; if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) msg_fatal("open memory stream: %m"); vstream_swap(VSTREAM_ERR, memory_stream); config_known_tcp_ports(tp->label, tp->config); vstream_swap(memory_stream, VSTREAM_ERR); if (vstream_fclose(memory_stream)) msg_fatal("close memory stream: %m"); if (strcmp(STR(msg_buf), tp->exp_warning) != 0) { msg_warn("test case %s: got error: \"%s\", want: \"%s\"", tp->label, STR(msg_buf), STRING_OR_NULL(tp->exp_warning)); test_failed = 1; } else { export = export_known_tcp_ports(export_buf); if (strcmp(export, tp->exp_export) != 0) { msg_warn("test case %s: got export: \"%s\", want: \"%s\"", tp->label, export, tp->exp_export); test_failed = 1; } clear_known_tcp_ports(); VSTRING_RESET(msg_buf); VSTRING_TERMINATE(msg_buf); } if (test_failed) { msg_info("%s: FAIL", tp->label); fail++; } else { msg_info("%s: PASS", tp->label); pass++; } } msg_info("PASS=%d FAIL=%d", pass, fail); vstring_free(msg_buf); vstring_free(export_buf); exit(fail != 0); } #endif