xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/config_known_tcp_ports.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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