1/*	$NetBSD: host_port.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2
3/*++
4/* NAME
5/*	host_port 3
6/* SUMMARY
7/*	split string into host and port, destroy string
8/* SYNOPSIS
9/*	#include <host_port.h>
10/*
11/*	const char *host_port(string, host, def_host, port, def_service)
12/*	char	*string;
13/*	char	**host;
14/*	char	*def_host;
15/*	char	**port;
16/*	char	*def_service;
17/* DESCRIPTION
18/*	host_port() splits a string into substrings with the host
19/*	name or address, and the service name or port number.
20/*	The input string is modified.
21/*
22/*	Host/domain names are validated with valid_utf8_hostname(),
23/*	and host addresses are validated with valid_hostaddr().
24/*
25/*	The following input formats are understood (null means
26/*	a null pointer argument):
27/*
28/*	When def_service is not null, and def_host is null:
29/*
30/*		[host]:port, [host]:, [host]
31/*
32/*		host:port, host:, host
33/*
34/*	When def_host is not null, and def_service is null:
35/*
36/*		:port, port
37/*
38/*	Other combinations of def_service and def_host are
39/*	not supported and produce undefined results.
40/* DIAGNOSTICS
41/*	The result is a null pointer in case of success.
42/*	In case of problems the result is a string pointer with
43/*	the problem type.
44/* CLIENT EXAMPLE
45/* .ad
46/* .fi
47/*	Typical client usage allows the user to omit the service port,
48/*	in which case the client connects to a pre-determined default
49/*	port:
50/* .nf
51/* .na
52/*
53/*	buf = mystrdup(endpoint);
54/*	if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0)
55/*	    msg_fatal("%s in \"%s\"", parse_error, endpoint);
56/*	if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
57/*	    msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
58/*	myfree(buf);
59/* SERVER EXAMPLE
60/* .ad
61/* .fi
62/*	Typical server usage allows the user to omit the host, meaning
63/*	listen on all available network addresses:
64/* .nf
65/* .na
66/*
67/*	buf = mystrdup(endpoint);
68/*	if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0)
69/*	    msg_fatal("%s in \"%s\"", parse_error, endpoint);
70/*	if (*host == 0)
71/*	    host = 0;
72/*	if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
73/*	    msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
74/*	myfree(buf);
75/* LICENSE
76/* .ad
77/* .fi
78/*	The Secure Mailer license must be distributed with this software.
79/* AUTHOR(S)
80/*	Wietse Venema
81/*	IBM T.J. Watson Research
82/*	P.O. Box 704
83/*	Yorktown Heights, NY 10598, USA
84/*--*/
85
86/* System library. */
87
88#include <sys_defs.h>
89#include <string.h>
90#include <ctype.h>
91
92/* Utility library. */
93
94#include <msg.h>
95#include <split_at.h>
96#include <stringops.h>			/* XXX util_utf8_enable */
97#include <valid_utf8_hostname.h>
98
99/* Global library. */
100
101#include <host_port.h>
102
103 /*
104  * Point-fix workaround. The libutil library should be email agnostic, but
105  * we can't rip up the library APIs in the stable releases.
106  */
107#include <string.h>
108#ifdef STRCASECMP_IN_STRINGS_H
109#include <strings.h>
110#endif
111#define IPV6_COL           "IPv6:"	/* RFC 2821 */
112#define IPV6_COL_LEN       (sizeof(IPV6_COL) - 1)
113#define HAS_IPV6_COL(str)  (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0)
114
115/* host_port - parse string into host and port, destroy string */
116
117const char *host_port(char *buf, char **host, char *def_host,
118		              char **port, char *def_service)
119{
120    char   *cp = buf;
121    int     ipv6 = 0;
122
123    /*-
124     * [host]:port, [host]:, [host].
125     * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr].
126     */
127    if (*cp == '[') {
128	++cp;
129	if ((ipv6 = HAS_IPV6_COL(cp)) != 0)
130	    cp += IPV6_COL_LEN;
131	*host = cp;
132	if ((cp = split_at(cp, ']')) == 0)
133	    return ("missing \"]\"");
134	if (*cp && *cp++ != ':')
135	    return ("garbage after \"]\"");
136	if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE))
137	    return ("malformed IPv6 address");
138	*port = *cp ? cp : def_service;
139    }
140
141    /*
142     * host:port, host:, host, :port, port.
143     */
144    else {
145	if ((cp = split_at_right(buf, ':')) != 0) {
146	    *host = *buf ? buf : def_host;
147	    *port = *cp ? cp : def_service;
148	} else {
149	    *host = def_host ? def_host : (*buf ? buf : 0);
150	    *port = def_service ? def_service : (*buf ? buf : 0);
151	}
152    }
153    if (*host == 0)
154	return ("missing host information");
155    if (*port == 0)
156	return ("missing service information");
157
158    /*
159     * Final sanity checks. We're still sloppy, allowing bare numerical
160     * network addresses instead of requiring proper [ipaddress] forms.
161     */
162    if (*host != def_host
163	&& !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE)
164	&& !valid_hostaddr(*host, DONT_GRIPE))
165	return ("valid hostname or network address required");
166    if (*port != def_service && ISDIGIT(**port) && !alldig(*port))
167	return ("garbage after numerical service");
168    return (0);
169}
170
171#ifdef TEST
172
173#include <vstream.h>
174#include <vstring.h>
175#include <vstring_vstream.h>
176
177#define STR(x) vstring_str(x)
178
179int     main(int unused_argc, char **unused_argv)
180{
181    VSTRING *in_buf = vstring_alloc(10);
182    VSTRING *parse_buf = vstring_alloc(10);
183    char   *host;
184    char   *port;
185    const char *err;
186
187    while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) {
188	vstream_printf(">> %s\n", STR(in_buf));
189	vstream_fflush(VSTREAM_OUT);
190	if (*STR(in_buf) == '#')
191	    continue;
192	vstring_strcpy(parse_buf, STR(in_buf));
193	if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) {
194	    msg_warn("%s in %s", err, STR(in_buf));
195	} else {
196	    vstream_printf("host %s port %s\n", host, port);
197	    vstream_fflush(VSTREAM_OUT);
198	}
199    }
200    vstring_free(in_buf);
201    vstring_free(parse_buf);
202    return (0);
203}
204
205#endif
206