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