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