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