Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: config_known_tcp_ports.c,v 1.3 2026/05/09 18:49:15 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 /* EXPECTED SYNTAX (ABNF)
     20 /*	configuration = empty | name-to-port *("," name-to-port)
     21 /*	name-to-port = 1*(name "=") port
     22 /* 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 
     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 
    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 
    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