1 /* $NetBSD: config_known_tcp_ports.c,v 1.3 2026/05/09 18:49:15 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* config_known_tcp_ports 3 6 /* SUMMARY 7 /* parse and store known TCP port configuration 8 /* SYNOPSIS 9 /* #include <config_known_tcp_ports.h> 10 /* 11 /* void config_known_tcp_ports( 12 /* const char *source, 13 /* const char *settings); 14 /* DESCRIPTION 15 /* config_known_tcp_ports() parses the known TCP port information 16 /* in the settings argument, and reports any warnings to the standard 17 /* error stream. The source argument is used to provide warning 18 /* context. It typically is a configuration parameter name. 19 /* EXPECTED SYNTAX (ABNF) 20 /* configuration = empty | name-to-port *("," name-to-port) 21 /* name-to-port = 1*(name "=") port 22 /* EXAMPLES 23 /* In the example below, the whitespace is optional. 24 /* smtp = 25, smtps = submissions = 465, submission = 587 25 /* LICENSE 26 /* .ad 27 /* .fi 28 /* The Secure Mailer license must be distributed with this software. 29 /* AUTHOR(S) 30 /* Wietse Venema 31 /* Google, Inc. 32 /* 111 8th Avenue 33 /* New York, NY 10011, USA 34 /*--*/ 35 36 /* 37 * System library. 38 */ 39 #include <sys_defs.h> 40 41 /* 42 * Utility library. 43 */ 44 #include <argv.h> 45 #include <known_tcp_ports.h> 46 #include <msg.h> 47 #include <mymalloc.h> 48 #include <stringops.h> 49 50 /* 51 * Application-specific. 52 */ 53 #include <config_known_tcp_ports.h> 54 55 /* config_known_tcp_ports - parse configuration and store associations */ 56 57 void config_known_tcp_ports(const char *source, const char *settings) 58 { 59 ARGV *associations; 60 ARGV *association; 61 char **cpp; 62 63 clear_known_tcp_ports(); 64 65 /* 66 * The settings is in the form of associations separated by comma. Split 67 * it into separate associations. 68 */ 69 associations = argv_split(settings, ","); 70 if (associations->argc == 0) { 71 argv_free(associations); 72 return; 73 } 74 75 /* 76 * Each association is in the form of "1*(name =) port". We use 77 * argv_split() to carve this up, then we use mystrtok() to validate the 78 * individual fragments. But first we prepend and append space so that we 79 * get sensible results when an association starts or ends in "=". 80 */ 81 for (cpp = associations->argv; *cpp != 0; cpp++) { 82 char *temp = concatenate(" ", *cpp, " ", (char *) 0); 83 84 association = argv_split_at(temp, '='); 85 myfree(temp); 86 87 if (association->argc == 0) { 88 /* empty, ignore */ ; 89 } else if (association->argc == 1) { 90 msg_warn("%s: in \"%s\" is not in \"name = value\" form", 91 source, *cpp); 92 } else { 93 char *bp; 94 char *lhs; 95 char *rhs; 96 const char *err = 0; 97 int n; 98 99 bp = association->argv[association->argc - 1]; 100 if ((rhs = mystrtok(&bp, CHARS_SPACE)) == 0) { 101 err = "missing port value after \"=\""; 102 } else if (mystrtok(&bp, CHARS_SPACE) != 0) { 103 err = "whitespace in port number"; 104 } else { 105 for (n = 0; n < association->argc - 1; n++) { 106 const char *new_err; 107 108 bp = association->argv[n]; 109 if ((lhs = mystrtok(&bp, CHARS_SPACE)) == 0) { 110 new_err = "missing service name before \"=\""; 111 } else if (mystrtok(&bp, CHARS_SPACE) != 0) { 112 new_err = "whitespace in service name"; 113 } else { 114 new_err = add_known_tcp_port(lhs, rhs); 115 } 116 if (new_err != 0 && err == 0) 117 err = new_err; 118 } 119 } 120 if (err != 0) { 121 msg_warn("%s: in \"%s\": %s", source, *cpp, err); 122 } 123 } 124 argv_free(association); 125 } 126 argv_free(associations); 127 } 128 129 #ifdef TEST 130 131 #include <stdlib.h> 132 #include <string.h> 133 #include <msg_vstream.h> 134 135 #define STR(x) vstring_str(x) 136 137 /* TODO(wietse) make this a proper VSTREAM interface */ 138 139 /* vstream_swap - kludge to capture output for testing */ 140 141 static void vstream_swap(VSTREAM *one, VSTREAM *two) 142 { 143 VSTREAM save; 144 145 save = *one; 146 *one = *two; 147 *two = save; 148 } 149 150 struct test_case { 151 const char *label; /* identifies test case */ 152 const char *config; /* configuration under test */ 153 const char *exp_warning; /* expected warning or null */ 154 const char *exp_export; /* expected export or null */ 155 }; 156 157 static struct test_case test_cases[] = { 158 {"good", 159 /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24", 160 /* warning */ "", 161 /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" 162 }, 163 {"equal-equal", 164 /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24", 165 /* warning */ "config_known_tcp_ports: warning: equal-equal: " 166 "in \" smtps == submissions = 465\": missing service name before " 167 "\"=\"\n", 168 /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" 169 }, 170 {"port test 1", 171 /* config */ "smtps = submission =", 172 /* warning */ "config_known_tcp_ports: warning: port test 1: " 173 "in \"smtps = submission =\": missing port value after \"=\"\n", 174 /* export */ "" 175 }, 176 {"port test 2", 177 /* config */ "smtps = submission = 4 65", 178 /* warning */ "config_known_tcp_ports: warning: port test 2: " 179 "in \"smtps = submission = 4 65\": whitespace in port number\n", 180 /* export */ "" 181 }, 182 {"port test 3", 183 /* config */ "lmtp = 24, smtps = submission = foo", 184 /* warning */ "config_known_tcp_ports: warning: port test 3: " 185 "in \" smtps = submission = foo\": non-numerical service port\n", 186 /* export */ "lmtp=24" 187 }, 188 {"service name test 1", 189 /* config */ "smtps = sub mission = 465", 190 /* warning */ "config_known_tcp_ports: warning: service name test 1: " 191 "in \"smtps = sub mission = 465\": whitespace in service name\n", 192 /* export */ "smtps=465" 193 }, 194 {"service name test 2", 195 /* config */ "lmtp = 24, smtps = 1234 = submissions = 465", 196 /* warning */ "config_known_tcp_ports: warning: service name test 2: " 197 "in \" smtps = 1234 = submissions = 465\": numerical service name\n", 198 /* export */ "lmtp=24 smtps=465 submissions=465" 199 }, 200 0, 201 }; 202 203 int main(int argc, char **argv) 204 { 205 VSTRING *export_buf; 206 struct test_case *tp; 207 int pass = 0; 208 int fail = 0; 209 int test_failed; 210 const char *export; 211 VSTRING *msg_buf; 212 VSTREAM *memory_stream; 213 214 #define STRING_OR_NULL(s) ((s) ? (s) : "(null)") 215 216 msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR); 217 218 export_buf = vstring_alloc(100); 219 msg_buf = vstring_alloc(100); 220 for (tp = test_cases; tp->label != 0; tp++) { 221 test_failed = 0; 222 if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) 223 msg_fatal("open memory stream: %m"); 224 vstream_swap(VSTREAM_ERR, memory_stream); 225 config_known_tcp_ports(tp->label, tp->config); 226 vstream_swap(memory_stream, VSTREAM_ERR); 227 if (vstream_fclose(memory_stream)) 228 msg_fatal("close memory stream: %m"); 229 if (strcmp(STR(msg_buf), tp->exp_warning) != 0) { 230 msg_warn("test case %s: got error: \"%s\", want: \"%s\"", 231 tp->label, STR(msg_buf), 232 STRING_OR_NULL(tp->exp_warning)); 233 test_failed = 1; 234 } else { 235 export = export_known_tcp_ports(export_buf); 236 if (strcmp(export, tp->exp_export) != 0) { 237 msg_warn("test case %s: got export: \"%s\", want: \"%s\"", 238 tp->label, export, tp->exp_export); 239 test_failed = 1; 240 } 241 clear_known_tcp_ports(); 242 VSTRING_RESET(msg_buf); 243 VSTRING_TERMINATE(msg_buf); 244 } 245 if (test_failed) { 246 msg_info("%s: FAIL", tp->label); 247 fail++; 248 } else { 249 msg_info("%s: PASS", tp->label); 250 pass++; 251 } 252 } 253 msg_info("PASS=%d FAIL=%d", pass, fail); 254 vstring_free(msg_buf); 255 vstring_free(export_buf); 256 exit(fail != 0); 257 } 258 259 #endif 260