1 /* $Id: innbind.c 9767 2014-12-07 21:13:43Z iulius $
2 **
3 ** Helper program to bind a socket to a low-numbered port.
4 **
5 ** Written by Russ Allbery <eagle@eyrie.org>
6 */
7
8 #include "config.h"
9 #include "clibrary.h"
10 #include "portable/socket.h"
11 #include <errno.h>
12 #ifdef HAVE_STREAMS_SENDFD
13 # include <stropts.h>
14 #endif
15 #include <syslog.h>
16
17 #include "inn/libinn.h"
18 #include "inn/messages.h"
19 #include "inn/newsuser.h"
20 #include "inn/vector.h"
21
22 /* Macros to set the len attribute of sockaddrs. */
23 #if HAVE_STRUCT_SOCKADDR_SA_LEN
24 # define sin_set_length(s) ((s)->sin_len = sizeof(struct sockaddr_in))
25 # define sin6_set_length(s) ((s)->sin6_len = sizeof(struct sockaddr_in6))
26 #else
27 # define sin_set_length(s) /* empty */
28 # define sin6_set_length(s) /* empty */
29 #endif
30
31 /* INND_PORT is the additional port specified at configure time to which the
32 news user should be allowed to bind. If it's not set, set it to 119 (which
33 will cause it to have no effect). I hate #ifdef in code, can you tell? */
34 #ifndef INND_PORT
35 # define INND_PORT 119
36 #endif
37
38 /* Holds the information about a network socket to bind. */
39 struct binding {
40 int fd;
41 int family;
42 char *address;
43 unsigned short port;
44 };
45
46
47 /*
48 ** Convert a string to a number with error checking, returning true if the
49 ** number was parsed correctly and false otherwise. Stores the converted
50 ** number in the second argument. Equivalent to calling strtol, but with the
51 ** base always fixed at 10, with checking of errno, ensuring that all of the
52 ** string is consumed, and checking that the resulting number is positive.
53 */
54 static bool
convert_string(const char * string,long * result)55 convert_string(const char *string, long *result)
56 {
57 char *end;
58
59 if (*string == '\0')
60 return false;
61 errno = 0;
62 *result = strtol(string, &end, 10);
63 if (errno != 0 || *end != '\0' || *result < 0)
64 return false;
65 return true;
66 }
67
68
69 /*
70 ** Parse a command-line argument into a struct binding. The command line
71 ** argument is four comma-separated values: the file descriptor, the family,
72 ** the listening address, and the port number. The caller is responsible for
73 ** freeing the address attribute of the supplied binding struct, although if
74 ** a binding struct is passed in for use and has a non-NULL address, it will
75 ** be freed first.
76 */
77 static void
parse_argument(const char * string,struct binding * binding)78 parse_argument(const char *string, struct binding *binding)
79 {
80 struct vector *spec;
81 long value;
82
83 /* Do the initial parse and allocate our data structures. */
84 spec = vector_split(string, ',', NULL);
85 if (spec->count != 4)
86 die("invalid command-line argument %s", string);
87
88 /* Get the file descriptor, address family, and port. */
89 if (!convert_string(spec->strings[0], &value))
90 die("invalid file descriptor %s in %s", spec->strings[0], string);
91 binding->fd = value;
92 if (!convert_string(spec->strings[1], &value))
93 die("invalid protocol family %s in %s", spec->strings[1], string);
94 binding->family = value;
95 if (binding->address != NULL)
96 free(binding->address);
97 binding->address = xstrdup(spec->strings[2]);
98 if (!convert_string(spec->strings[3], &value))
99 die("invalid port number %s in %s", spec->strings[3], string);
100 if (value == 0)
101 die("port may not be zero in %s", string);
102 binding->port = value;
103
104 /* Done. Clean up. */
105 vector_free(spec);
106 }
107
108
109 /*
110 ** Bind an IPv4 address, given the file descriptor, string giving the
111 ** address, and the port. The fourth argument is the full binding
112 ** specification for error reporting. Returns true on success, false if
113 ** binding failed due to permission denied. Die on any other failure.
114 */
115 static bool
bind_ipv4(int fd,const char * address,unsigned short port,const char * spec)116 bind_ipv4(int fd, const char *address, unsigned short port, const char *spec)
117 {
118 struct sockaddr_in server;
119 struct in_addr addr;
120
121 memset(&server, '\0', sizeof(server));
122 server.sin_family = AF_INET;
123 server.sin_port = htons(port);
124 if (!inet_aton(address, &addr))
125 die("invalid IPv4 address %s in %s", address, spec);
126 server.sin_addr = addr;
127 sin_set_length(&server);
128 if (bind(fd, (struct sockaddr *) &server, sizeof(server)) < 0) {
129 if (errno == EACCES)
130 return false;
131 else
132 sysdie("cannot bind socket for %s", spec);
133 }
134 return true;
135 }
136
137
138 /*
139 ** Bind an IPv6 address, given the file descriptor, string giving the
140 ** address, and the port. The fourth argument is the full binding
141 ** specification for error reporting. Returns true on success, false if
142 ** binding failed due to permission denied. Die on any other failure.
143 */
144 #ifdef HAVE_INET6
145 static bool
bind_ipv6(int fd,const char * address,unsigned short port,const char * spec)146 bind_ipv6(int fd, const char *address, unsigned short port, const char *spec)
147 {
148 struct sockaddr_in6 server;
149 struct in6_addr addr;
150
151 memset(&server, '\0', sizeof(server));
152 server.sin6_family = AF_INET6;
153 server.sin6_port = htons(port);
154 if (inet_pton(AF_INET6, address, &addr) < 1)
155 die("invalid IPv6 address %s in %s", address, spec);
156 server.sin6_addr = addr;
157 sin6_set_length(&server);
158 if (bind(fd, (struct sockaddr *) &server, sizeof(server)) < 0) {
159 if (errno == EACCES)
160 return false;
161 else
162 sysdie("cannot bind socket for %s", spec);
163 }
164 return true;
165 }
166 #endif /* HAVE_INET6 */
167
168
169 /*
170 ** Given a struct binding, bind that file descriptor. Also takes the
171 ** command-line argument for error reporting. Returns true on success, false
172 ** if binding failed due to permission denied. Die on any other failure.
173 */
174 static bool
bind_address(struct binding * binding,const char * spec)175 bind_address(struct binding *binding, const char *spec)
176 {
177 int fd = binding->fd;
178 unsigned short port = binding->port;
179 int type;
180 socklen_t length;
181
182 /* Make sure that we're allowed to bind to that port. */
183 if (port < 1024 && port != 119 && port != 433 && port != 563
184 && port != INND_PORT)
185 die("cannot bind to restricted port %hu in %s", port, spec);
186
187 /* Sanity check on the socket. */
188 length = sizeof(type);
189 if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &length) < 0)
190 sysdie("cannot get socket options for file descriptor %d", fd);
191 if (type != SOCK_STREAM)
192 die("invalid file descriptor %d: not SOCK_STREAM", fd);
193
194 /* Based on the address family, parse either an IPv4 or IPv6 address. */
195 if (binding->family == AF_INET)
196 return bind_ipv4(fd, binding->address, port, spec);
197 #ifdef HAVE_INET6
198 else if (binding->family == AF_INET6)
199 return bind_ipv6(fd, binding->address, port, spec);
200 #endif
201 else
202 die("unknown protocol family %d in %s", binding->family, spec);
203 }
204
205
206 /*
207 ** Given a struct binding, create a socket for it and fill in the file
208 ** descriptor. Also takes the command-line argument for error reporting.
209 ** Dies on any failure.
210 */
211 static void
create_socket(struct binding * binding,const char * spec)212 create_socket(struct binding *binding, const char *spec)
213 {
214 int fd;
215 #if defined(SO_REUSEADDR) || defined(IPV6_V6ONLY)
216 int flag;
217 #endif
218
219 /* Create the socket. */
220 if (binding->family == AF_INET)
221 fd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
222 #ifdef HAVE_INET6
223 else if (binding->family == AF_INET6)
224 fd = socket(PF_INET6, SOCK_STREAM, IPPROTO_IP);
225 #endif
226 else
227 die("unknown protocol family %d in %s", binding->family, spec);
228 if (fd < -1)
229 sysdie("cannot create socket for %s", spec);
230
231 /* Mark it reusable if possible. */
232 #ifdef SO_REUSEADDR
233 flag = 1;
234 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0)
235 sysdie("cannot mark socket reusable for %s", spec);
236 #endif
237
238 /* Mark it IPv6 only if possible. */
239 #ifdef IPV6_V6ONLY
240 flag = 1;
241 if (binding->family == AF_INET6 &&
242 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0)
243 sysdie("cannot mark socket IPv6 only for %s", spec);
244 #endif
245
246 /* Fill in the struct. */
247 binding->fd = fd;
248 }
249
250
251 /*
252 ** Given a file descriptor, attempt to pass it via stdout. Die on any error.
253 ** Currently, file descriptor passing is only implemented for systems that
254 ** use STREAMS.
255 */
256 #ifdef HAVE_STREAMS_SENDFD
257 static void
send_fd(int fd)258 send_fd(int fd)
259 {
260 if (isastream(STDOUT_FILENO) != 1)
261 die("cannot pass file descriptor: stdout is not a stream");
262 if (ioctl(STDOUT_FILENO, I_SENDFD, fd) < 0)
263 sysdie("cannot pass file descriptor");
264 }
265 #else /* !HAVE_STREAMS_SENDFD */
266 static void
send_fd(int fd UNUSED)267 send_fd(int fd UNUSED)
268 {
269 die("cannot pass file descriptor: STREAMS not supported");
270 }
271 #endif /* !HAVE_STREAMS_SENDFD */
272
273
274 int
main(int argc,char * argv[])275 main(int argc, char *argv[])
276 {
277 uid_t real_uid, uid;
278 int i;
279 bool done;
280 struct binding binding = { 0, 0, NULL, 0 };
281 bool force_sendfd = false;
282
283 /* Set up the error handlers. Errors go to stderr and to syslog with a
284 priority of LOG_CRIT. This priority level is too high, but it's chosen
285 to match innd. */
286 openlog("innbind", LOG_CONS, LOG_INN_PROG);
287 message_handlers_die(2, message_log_stderr, message_log_syslog_crit);
288 message_program_name = "innbind";
289
290 /* If we're running privileged (effective and real UIDs are different),
291 convert runasuser to a UID and exit if run by another user. Don't do
292 this if we're not running privileged to make installations that don't
293 need privileged ports easier and to make testing easier. */
294 real_uid = getuid();
295 if (real_uid != geteuid()) {
296 get_news_uid_gid(&uid, false, true);
297 if (real_uid != uid) {
298 die("must be run by runasuser (%lu), not %lu",
299 (unsigned long) uid, (unsigned long) real_uid);
300 }
301 }
302
303 /* If the first argument is -p, force creation of the socket and file
304 descriptor passing rather than even attempting to bind the socket
305 first. */
306 if (argc > 1 && strcmp(argv[1], "-p") == 0) {
307 force_sendfd = true;
308 argc--;
309 argv++;
310 }
311 if (argc < 2)
312 die("no addresses specified");
313
314 /* Walk the argument list and try to bind each argument. For each
315 successful bind, print "ok\n" to stdout. For each bind that fails with
316 permission denied, print "no\n" to stdout and then immediately attempt
317 to create a new file descriptor and pass it back over stdout. */
318 for (i = 1; i < argc; i++) {
319 parse_argument(argv[i], &binding);
320 done = false;
321 if (!force_sendfd)
322 done = bind_address(&binding, argv[i]);
323 if (done)
324 write(STDOUT_FILENO, "ok\n", 3);
325 else {
326 write(STDOUT_FILENO, "no\n", 3);
327 create_socket(&binding, argv[i]);
328 if (!bind_address(&binding, argv[i]))
329 sysdie("cannot bind socket for %s", argv[i]);
330 send_fd(binding.fd);
331 }
332 }
333 exit(0);
334 }
335