summaryrefslogtreecommitdiffstats
path: root/src/global/config_known_tcp_ports.c
blob: 563bbd356d4cdc7cccf9e5514e4a1d29c269c5fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*++
/* NAME
/*	config_known_tcp_ports 3
/* SUMMARY
/*	parse and store known TCP port configuration
/* SYNOPSIS
/*	#include <config_known_tcp_ports.h>
/*
/*	void	config_known_tcp_ports(
/*	const char *source,
/*	const char *settings);
/* DESCRIPTION
/*	config_known_tcp_ports() parses the known TCP port information
/*	in the settings argument, and reports any warnings to the standard
/*	error stream. The source argument is used to provide warning
/*	context. It typically is a configuration parameter name.
/* .SH EXPECTED SYNTAX (ABNF)
/*	configuration = empty | name-to-port *("," name-to-port)
/*	name-to-port = 1*(name "=") port
/* SH EXAMPLES
/*	In the example below, the whitespace is optional.
/*	smtp = 25, smtps = submissions = 465, submission = 587
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>

 /*
  * Utility library.
  */
#include <argv.h>
#include <known_tcp_ports.h>
#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>

 /*
  * Application-specific.
  */
#include <config_known_tcp_ports.h>

/* config_known_tcp_ports - parse configuration and store associations */

void    config_known_tcp_ports(const char *source, const char *settings)
{
    ARGV   *associations;
    ARGV   *association;
    char  **cpp;

    clear_known_tcp_ports();

    /*
     * The settings is in the form of associations separated by comma. Split
     * it into separate associations.
     */
    associations = argv_split(settings, ",");
    if (associations->argc == 0) {
	argv_free(associations);
	return;
    }

    /*
     * Each association is in the form of "1*(name =) port". We use
     * argv_split() to carve this up, then we use mystrtok() to validate the
     * individual fragments. But first we prepend and append space so that we
     * get sensible results when an association starts or ends in "=".
     */
    for (cpp = associations->argv; *cpp != 0; cpp++) {
	char   *temp = concatenate(" ", *cpp, " ", (char *) 0);

	association = argv_split_at(temp, '=');
	myfree(temp);

	if (association->argc == 0) {
	     /* empty, ignore */ ;
	} else if (association->argc == 1) {
	    msg_warn("%s: in \"%s\" is not in \"name = value\" form",
		     source, *cpp);
	} else {
	    char   *bp;
	    char   *lhs;
	    char   *rhs;
	    const char *err = 0;
	    int     n;

	    bp = association->argv[association->argc - 1];
	    if ((rhs = mystrtok(&bp, CHARS_SPACE)) == 0) {
		err = "missing port value after \"=\"";
	    } else if (mystrtok(&bp, CHARS_SPACE) != 0) {
		err = "whitespace in port number";
	    } else {
		for (n = 0; n < association->argc - 1; n++) {
		    const char *new_err;

		    bp = association->argv[n];
		    if ((lhs = mystrtok(&bp, CHARS_SPACE)) == 0) {
			new_err = "missing service name before \"=\"";
		    } else if (mystrtok(&bp, CHARS_SPACE) != 0) {
			new_err = "whitespace in service name";
		    } else {
			new_err = add_known_tcp_port(lhs, rhs);
		    }
		    if (new_err != 0 && err == 0)
			err = new_err;
		}
	    }
	    if (err != 0) {
		msg_warn("%s: in \"%s\": %s", source, *cpp, err);
	    }
	}
	argv_free(association);
    }
    argv_free(associations);
}

#ifdef TEST

#include <stdlib.h>
#include <string.h>
#include <msg_vstream.h>

#define STR(x) vstring_str(x)

 /* TODO(wietse) make this a proper VSTREAM interface */

/* vstream_swap - kludge to capture output for testing */

static void vstream_swap(VSTREAM *one, VSTREAM *two)
{
    VSTREAM save;

    save = *one;
    *one = *two;
    *two = save;
}

struct test_case {
    const char *label;			/* identifies test case */
    const char *config;			/* configuration under test */
    const char *exp_warning;		/* expected warning or null */
    const char *exp_export;		/* expected export or null */
};

static struct test_case test_cases[] = {
    {"good",
	 /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24",
	 /* warning */ "",
	 /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
    },
    {"equal-equal",
	 /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24",
	 /* warning */ "config_known_tcp_ports: warning: equal-equal: "
	"in \" smtps == submissions = 465\": missing service name before "
	"\"=\"\n",
	 /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
    },
    {"port test 1",
	 /* config */ "smtps = submission =",
	 /* warning */ "config_known_tcp_ports: warning: port test 1: "
	"in \"smtps = submission =\": missing port value after \"=\"\n",
	 /* export */ ""
    },
    {"port test 2",
	 /* config */ "smtps = submission = 4 65",
	 /* warning */ "config_known_tcp_ports: warning: port test 2: "
	"in \"smtps = submission = 4 65\": whitespace in port number\n",
	 /* export */ ""
    },
    {"port test 3",
	 /* config */ "lmtp = 24, smtps = submission = foo",
	 /* warning */ "config_known_tcp_ports: warning: port test 3: "
	"in \" smtps = submission = foo\": non-numerical service port\n",
	 /* export */ "lmtp=24"
    },
    {"service name test 1",
	 /* config */ "smtps = sub mission = 465",
	 /* warning */ "config_known_tcp_ports: warning: service name test 1: "
	"in \"smtps = sub mission = 465\": whitespace in service name\n",
	 /* export */ "smtps=465"
    },
    {"service name test 2",
	 /* config */ "lmtp = 24, smtps = 1234 = submissions = 465",
	 /* warning */ "config_known_tcp_ports: warning: service name test 2: "
	"in \" smtps = 1234 = submissions = 465\": numerical service name\n",
	 /* export */ "lmtp=24 smtps=465 submissions=465"
    },
    0,
};

int     main(int argc, char **argv)
{
    VSTRING *export_buf;
    struct test_case *tp;
    int     pass = 0;
    int     fail = 0;
    int     test_failed;
    const char *export;
    VSTRING *msg_buf;
    VSTREAM *memory_stream;

#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")

    msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR);

    export_buf = vstring_alloc(100);
    msg_buf = vstring_alloc(100);
    for (tp = test_cases; tp->label != 0; tp++) {
	test_failed = 0;
	if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
	    msg_fatal("open memory stream: %m");
	vstream_swap(VSTREAM_ERR, memory_stream);
	config_known_tcp_ports(tp->label, tp->config);
	vstream_swap(memory_stream, VSTREAM_ERR);
	if (vstream_fclose(memory_stream))
	    msg_fatal("close memory stream: %m");
	if (strcmp(STR(msg_buf), tp->exp_warning) != 0) {
	    msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
		     tp->label, STR(msg_buf),
		     STRING_OR_NULL(tp->exp_warning));
	    test_failed = 1;
	} else {
	    export = export_known_tcp_ports(export_buf);
	    if (strcmp(export, tp->exp_export) != 0) {
		msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
			 tp->label, export, tp->exp_export);
		test_failed = 1;
	    }
	    clear_known_tcp_ports();
	    VSTRING_RESET(msg_buf);
	    VSTRING_TERMINATE(msg_buf);
	}
	if (test_failed) {
	    msg_info("%s: FAIL", tp->label);
	    fail++;
	} else {
	    msg_info("%s: PASS", tp->label);
	    pass++;
	}
    }
    msg_info("PASS=%d FAIL=%d", pass, fail);
    vstring_free(msg_buf);
    vstring_free(export_buf);
    exit(fail != 0);
}

#endif