diff options
Diffstat (limited to 'src/util/host_port.c')
-rw-r--r-- | src/util/host_port.c | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/src/util/host_port.c b/src/util/host_port.c new file mode 100644 index 0000000..c4e8616 --- /dev/null +++ b/src/util/host_port.c @@ -0,0 +1,203 @@ +/*++ +/* NAME +/* host_port 3 +/* SUMMARY +/* split string into host and port, destroy string +/* SYNOPSIS +/* #include <host_port.h> +/* +/* const char *host_port(string, host, def_host, port, def_service) +/* char *string; +/* char **host; +/* char *def_host; +/* char **port; +/* char *def_service; +/* DESCRIPTION +/* host_port() splits a string into substrings with the host +/* name or address, and the service name or port number. +/* The input string is modified. +/* +/* Host/domain names are validated with valid_utf8_hostname(), +/* and host addresses are validated with valid_hostaddr(). +/* +/* The following input formats are understood (null means +/* a null pointer argument): +/* +/* When def_service is not null, and def_host is null: +/* +/* [host]:port, [host]:, [host] +/* +/* host:port, host:, host +/* +/* When def_host is not null, and def_service is null: +/* +/* :port, port +/* +/* Other combinations of def_service and def_host are +/* not supported and produce undefined results. +/* DIAGNOSTICS +/* The result is a null pointer in case of success. +/* In case of problems the result is a string pointer with +/* the problem type. +/* CLIENT EXAMPLE +/* .ad +/* .fi +/* Typical client usage allows the user to omit the service port, +/* in which case the client connects to a pre-determined default +/* port: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* SERVER EXAMPLE +/* .ad +/* .fi +/* Typical server usage allows the user to omit the host, meaning +/* listen on all available network addresses: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if (*host == 0) +/* host = 0; +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <split_at.h> +#include <stringops.h> /* XXX util_utf8_enable */ +#include <valid_utf8_hostname.h> + +/* Global library. */ + +#include <host_port.h> + + /* + * Point-fix workaround. The libutil library should be email agnostic, but + * we can't rip up the library APIs in the stable releases. + */ +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#define IPV6_COL "IPv6:" /* RFC 2821 */ +#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1) +#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0) + +/* host_port - parse string into host and port, destroy string */ + +const char *host_port(char *buf, char **host, char *def_host, + char **port, char *def_service) +{ + char *cp = buf; + int ipv6 = 0; + + /*- + * [host]:port, [host]:, [host]. + * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr]. + */ + if (*cp == '[') { + ++cp; + if ((ipv6 = HAS_IPV6_COL(cp)) != 0) + cp += IPV6_COL_LEN; + *host = cp; + if ((cp = split_at(cp, ']')) == 0) + return ("missing \"]\""); + if (*cp && *cp++ != ':') + return ("garbage after \"]\""); + if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE)) + return ("malformed IPv6 address"); + *port = *cp ? cp : def_service; + } + + /* + * host:port, host:, host, :port, port. + */ + else { + if ((cp = split_at_right(buf, ':')) != 0) { + *host = *buf ? buf : def_host; + *port = *cp ? cp : def_service; + } else { + *host = def_host ? def_host : (*buf ? buf : 0); + *port = def_service ? def_service : (*buf ? buf : 0); + } + } + if (*host == 0) + return ("missing host information"); + if (*port == 0) + return ("missing service information"); + + /* + * Final sanity checks. We're still sloppy, allowing bare numerical + * network addresses instead of requiring proper [ipaddress] forms. + */ + if (*host != def_host + && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE) + && !valid_hostaddr(*host, DONT_GRIPE)) + return ("valid hostname or network address required"); + if (*port != def_service && ISDIGIT(**port) && !alldig(*port)) + return ("garbage after numerical service"); + return (0); +} + +#ifdef TEST + +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> + +#define STR(x) vstring_str(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *in_buf = vstring_alloc(10); + VSTRING *parse_buf = vstring_alloc(10); + char *host; + char *port; + const char *err; + + while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) { + vstream_printf(">> %s\n", STR(in_buf)); + vstream_fflush(VSTREAM_OUT); + if (*STR(in_buf) == '#') + continue; + vstring_strcpy(parse_buf, STR(in_buf)); + if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) { + msg_warn("%s in %s", err, STR(in_buf)); + } else { + vstream_printf("host %s port %s\n", host, port); + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(in_buf); + vstring_free(parse_buf); + return (0); +} + +#endif |