1 /* $NetBSD: config_known_tcp_ports.c,v 1.2 2022/10/08 16:12:45 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 /* .SH EXPECTED SYNTAX (ABNF)
20 /* configuration = empty | name-to-port *("," name-to-port)
21 /* name-to-port = 1*(name "=") port
22 /* SH 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
config_known_tcp_ports(const char * source,const char * settings)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
vstream_swap(VSTREAM * one,VSTREAM * two)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
main(int argc,char ** argv)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