1 /*
2 **  External authenticator support.
3 **
4 **  Run an external resolver or authenticator to determine the username of the
5 **  client and return that information to INN.  For more information about the
6 **  protocol used, see doc/external-auth.
7 */
8 
9 #include "portable/system.h"
10 
11 #include <errno.h>
12 #include <signal.h>
13 #include <sys/wait.h>
14 
15 #include "inn/buffer.h"
16 #include "inn/messages.h"
17 #include "inn/vector.h"
18 #include "nnrpd.h"
19 
20 /* Holds the details about a running child process. */
21 struct process {
22     pid_t pid;
23     int read_fd;  /* Read from child. */
24     int write_fd; /* Write to child. */
25     int error_fd;
26 };
27 
28 
29 /*
30 **  Given the client information struct, a string indicating the program to
31 **  run (possibly including arguments) and the directory in which to look for
32 **  the command if it's not fully qualified, start that program and return a
33 **  struct process providing the PID and file descriptors.
34 */
35 static struct process *
start_process(struct client * client,const char * command,const char * dir)36 start_process(struct client *client, const char *command, const char *dir)
37 {
38     struct process *process;
39     int rd[2], wr[2], er[2];
40     pid_t pid;
41     char *path;
42     struct vector *args;
43 
44     /* Parse the command and find the path to the binary. */
45     args = vector_split_space(command, NULL);
46     path = args->strings[0];
47     if (path[0] != '/') {
48         path = concatpath(dir, path);
49     }
50 
51     /* Set up the pipes and run the program. */
52     if (pipe(rd) < 0 || pipe(wr) < 0 || pipe(er) < 0) {
53         syswarn("%s auth: cannot create pipe", client->host);
54         return NULL;
55     }
56     pid = fork();
57     switch (pid) {
58     case -1:
59         close(rd[0]);
60         close(rd[1]);
61         close(wr[0]);
62         close(wr[1]);
63         close(er[0]);
64         close(er[1]);
65         syswarn("%s auth: cannot fork", client->host);
66         return NULL;
67     case 0:
68         if (dup2(wr[0], 0) < 0 || dup2(rd[1], 1) < 0 || dup2(er[1], 2) < 0) {
69             syswarn("%s auth: cannot set up file descriptors", client->host);
70             _exit(1);
71         }
72         close(rd[0]);
73         close(rd[1]);
74         close(wr[0]);
75         close(wr[1]);
76         close(er[0]);
77         close(er[1]);
78         if (vector_exec(path, args) < 0) {
79             syswarn("%s auth: cannot exec %s", client->host, path);
80             _exit(1);
81         }
82     }
83 
84     /* In the parent.  Close excess file descriptors and build return. */
85     close(rd[1]);
86     close(wr[0]);
87     close(er[1]);
88     process = xmalloc(sizeof(struct process));
89     process->pid = pid;
90     process->read_fd = rd[0];
91     process->write_fd = wr[1];
92     process->error_fd = er[0];
93     return process;
94 }
95 
96 
97 /*
98 **  Handle a result line from the program which has already been
99 **  nul-terminated at the end of the line.  If User:<username> is seen, point
100 **  the second argument at newly allocated space for it.
101 */
102 static void
handle_result(struct client * client UNUSED,const char * line,char ** user)103 handle_result(struct client *client UNUSED, const char *line, char **user)
104 {
105     if (strncasecmp(line, "User:", strlen("User:")) == 0) {
106         if (*user != NULL)
107             free(*user);
108         *user = xstrdup(line + strlen("User:"));
109     }
110 }
111 
112 
113 /*
114 **  Handle an error line from the program by logging it.
115 */
116 static void
handle_error(struct client * client,const char * line,char ** user UNUSED)117 handle_error(struct client *client, const char *line, char **user UNUSED)
118 {
119     notice("%s auth: program error: %s", client->host, line);
120 }
121 
122 
123 /*
124 **  Read a line of data from the given file descriptor.  Return the number of
125 **  bytes read or -1 on buffer overflow.  Takes the file descriptor, the
126 **  buffer used for that file descriptor, and the handler function to call for
127 **  each line.  Points the fourth argument to a username, if one was found.
128 */
129 static ssize_t
output(struct client * client,int fd,struct buffer * buffer,void (* handler)(struct client *,const char *,char **),char ** user)130 output(struct client *client, int fd, struct buffer *buffer,
131        void (*handler)(struct client *, const char *, char **), char **user)
132 {
133     char *line;
134     char *start;
135     ssize_t count;
136 
137     /* Read the data. */
138     buffer_compact(buffer);
139     count = buffer_read(buffer, fd);
140     if (buffer->left >= buffer->size - 1)
141         return -1;
142     if (count < 0) {
143         syswarn("%s auth: read error", client->host);
144         return count;
145     }
146 
147     /* If reached end of file, process anything left as a line. */
148     if (count == 0) {
149         if (buffer->left > 0) {
150             buffer->data[buffer->used + buffer->left] = '\0';
151             handler(client, buffer->data + buffer->used, user);
152             buffer->used += buffer->left;
153             buffer->left = 0;
154         }
155         return count;
156     }
157 
158     /* Otherwise, break what we got up into lines and process each one. */
159     start = buffer->data + buffer->used;
160     line = memchr(start, '\n', buffer->left);
161     while (line != NULL) {
162         *line = '\0';
163         if (line > start && line[-1] == '\r')
164             line[-1] = '\0';
165         handler(client, start, user);
166         buffer->used += line - start + 1;
167         buffer->left -= line - start + 1;
168         start = buffer->data + buffer->used;
169         line = memchr(start, '\n', buffer->left);
170     }
171     return count;
172 }
173 
174 
175 /*
176 **  Wait for the program to produce output.  For each bit of output, call
177 **  handle_output with the appropriate handler function.  After end of file or
178 **  an error, check and report on the exit status.  Returns the username in
179 **  newly allocated memory, or NULL if none was found.
180 **
181 **  Currently, use a hard-coded five-second timeout for all programs.  This
182 **  might need to be configurable later.
183 */
184 static char *
handle_output(struct client * client,struct process * process)185 handle_output(struct client *client, struct process *process)
186 {
187     fd_set fds, rfds;
188     struct timeval tv;
189     int maxfd, status, fd;
190     ssize_t count;
191     pid_t result;
192     struct buffer *readbuf;
193     struct buffer *errorbuf;
194     double start, end;
195     bool done;
196     bool error_done = false;
197     bool killed = false;
198     bool errored = false;
199     char *user = NULL;
200 
201     FD_ZERO(&fds);
202     FD_SET(process->read_fd, &fds);
203     FD_SET(process->error_fd, &fds);
204     maxfd = process->read_fd > process->error_fd ? process->read_fd
205                                                  : process->error_fd;
206     readbuf = buffer_new();
207     buffer_resize(readbuf, 1024);
208     errorbuf = buffer_new();
209     buffer_resize(errorbuf, 1024);
210 
211     /* Loop until we get an error or end of file. */
212     while (1) {
213         tv.tv_sec = 5;
214         tv.tv_usec = 0;
215         rfds = fds;
216         start = TMRnow_double();
217         status = select(maxfd + 1, &rfds, NULL, NULL, &tv);
218         end = TMRnow_double();
219         IDLEtime += end - start;
220 
221         /* Check for select timeout or errors. */
222         if (status <= 0) {
223             if (status == 0)
224                 syswarn("%s auth: program timeout", client->host);
225             else {
226                 if (errno == EINTR)
227                     continue;
228                 syswarn("%s auth: select failed", client->host);
229             }
230             killed = true;
231             kill(process->pid, SIGTERM);
232             break;
233         }
234 
235         /* Read from the read and error file descriptors.  If we see EOF or an
236          * error from the read file descriptor, we're done.  If we see EOF or
237          * an error from the error file descriptor, remove it from the set but
238          * continue waiting for output from the read file descriptor .*/
239         done = false;
240         count = 0;
241         if (FD_ISSET(process->read_fd, &rfds)) {
242             fd = process->read_fd;
243             count = output(client, fd, readbuf, handle_result, &user);
244             if (count <= 0)
245                 done = true;
246         }
247         if (!error_done && FD_ISSET(process->error_fd, &rfds)) {
248             fd = process->error_fd;
249             count = output(client, fd, errorbuf, handle_error, &user);
250             if (count <= 0) {
251                 error_done = true;
252                 FD_CLR(fd, &fds);
253                 maxfd = process->read_fd;
254             }
255             if (count > 0)
256                 errored = true;
257         }
258         if (done) {
259             close(process->read_fd);
260             close(process->error_fd);
261             if (count < 0) {
262                 warn("%s auth: output too long from program", client->host);
263                 killed = true;
264                 kill(process->pid, SIGTERM);
265             }
266             break;
267         }
268     }
269     buffer_free(readbuf);
270     buffer_free(errorbuf);
271 
272     /* Wait for the program to exit. */
273     do {
274         result = waitpid(process->pid, &status, 0);
275     } while (result == -1 && errno == EINTR);
276     if (result != process->pid) {
277         syswarn("%s auth: cannot wait for program", client->host);
278         goto fail;
279     }
280 
281     /* Check the exit status and fall through to fail if the external
282      * authentication program didn't exit successfully. */
283     if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
284         return user;
285     else {
286         if (WIFSIGNALED(status) && (!killed || WTERMSIG(status) != SIGTERM))
287             notice("%s auth: program caught signal %d", client->host,
288                    WTERMSIG(status));
289         else if (WIFEXITED(status) && !errored)
290             notice("%s auth: program exited with status %d", client->host,
291                    WEXITSTATUS(status));
292     }
293 
294 fail:
295     if (user != NULL)
296         free(user);
297     return NULL;
298 }
299 
300 
301 /*
302 **  Append the standard connection information to the provided buffer.
303 */
304 static void
append_client_info(struct client * client,struct buffer * data)305 append_client_info(struct client *client, struct buffer *data)
306 {
307     if (*client->host)
308         buffer_append_sprintf(data, "ClientHost: %s\r\n", client->host);
309     if (*client->ip)
310         buffer_append_sprintf(data, "ClientIP: %s\r\n", client->ip);
311     if (client->port != 0)
312         buffer_append_sprintf(data, "ClientPort: %hu\r\n", client->port);
313     if (*client->serverip)
314         buffer_append_sprintf(data, "LocalIP: %s\r\n", client->serverip);
315     if (client->serverport != 0)
316         buffer_append_sprintf(data, "LocalPort: %hu\r\n", client->serverport);
317 }
318 
319 
320 /*
321 **  Execute a program to get the remote username.  Takes the client info, the
322 **  command to run, the subdirectory in which to look for programs, and
323 **  optional username and password information to pass to the program.
324 **  Returns the username in newly allocated memory if successful, NULL
325 **  otherwise.
326 */
327 char *
auth_external(struct client * client,const char * command,const char * directory,const char * username,const char * password)328 auth_external(struct client *client, const char *command,
329               const char *directory, const char *username,
330               const char *password)
331 {
332     char *user;
333     struct process *process;
334     struct buffer *input;
335 
336     /* Start the program. */
337     process = start_process(client, command, directory);
338     if (process == NULL)
339         return NULL;
340 
341     /* Feed it data. */
342     input = buffer_new();
343     append_client_info(client, input);
344     if (username != NULL)
345         buffer_append_sprintf(input, "ClientAuthname: %s\r\n", username);
346     if (password != NULL)
347         buffer_append_sprintf(input, "ClientPassword: %s\r\n", password);
348     buffer_append_sprintf(input, ".\r\n");
349     xwrite(process->write_fd, input->data, input->left);
350     close(process->write_fd);
351     buffer_free(input);
352 
353     /* Get the results. */
354     user = handle_output(client, process);
355     close(process->read_fd);
356     close(process->error_fd);
357     free(process);
358     return user;
359 }
360