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