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