summaryrefslogtreecommitdiffstats
path: root/src/util/host_port.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/host_port.c')
-rw-r--r--src/util/host_port.c203
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