OpenDNSSEC-enforcer  2.1.7
ods-enforcer.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2009 NLNet Labs. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
19  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
21  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
23  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  */
26 
32 #include "config.h"
33 
34 #include <signal.h>
35 #include <errno.h>
36 #include <fcntl.h> /* fcntl() */
37 #include <stdio.h> /* fprintf() */
38 #include <string.h> /* strerror(), strncmp(), strlen(), strcpy(), strncat() */
39 #include <strings.h> /* bzero() */
40 #include <sys/select.h> /* select(), FD_ZERO(), FD_SET(), FD_ISSET(), FD_CLR() */
41 #include <sys/socket.h> /* socket(), connect(), shutdown() */
42 #include <sys/un.h>
43 #include <unistd.h> /* exit(), read(), write() */
44 #include <getopt.h>
45 /* According to earlier standards, we need sys/time.h, sys/types.h, unistd.h for select() */
46 #include <sys/types.h>
47 #include <sys/time.h>
48 #include <stdlib.h>
49 #include <assert.h>
50 #ifdef HAVE_READLINE
51  /* cmd history */
52  #include <readline/readline.h>
53  #include <readline/history.h>
54 #endif
55 
56 #include "file.h"
57 #include "log.h"
58 #include "str.h"
59 #include "clientpipe.h"
60 
61 static const char* PROMPT = "cmd> ";
62 static const char* cli_str = "client";
63 
68 static void
69 usage(char* argv0, FILE* out)
70 {
71  fprintf(out, "Usage: %s [OPTION]... [COMMAND]\n", argv0);
72  fprintf(out,
73 "Simple command line interface to control the enforcer engine \n"
74 "daemon. If no command is given, the tool is going to interactive \n"
75 "mode. When the daemon is running 'ods-enforcer help' gives a full \n"
76 "list of available commands.\n");
77 
78  fprintf(out, "\nSupported options:\n");
79  fprintf(out, " -h | --help Show this help and exit.\n");
80  fprintf(out, " -V | --version Show version and exit.\n");
81  fprintf(out, " -s | --socket <file> Daemon socketfile \n"
82  " | (default %s).\n", OPENDNSSEC_ENFORCER_SOCKETFILE);
83 
84  fprintf(out, "\nBSD licensed, see LICENSE in source package for "
85  "details.\n");
86  fprintf(out, "Version %s. Report bugs to <%s>.\n",
87  PACKAGE_VERSION, PACKAGE_BUGREPORT);
88 }
89 
94 static void
95 version(FILE* out)
96 {
97  fprintf(out, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
98 }
99 
117 /* return 0 or (1 and exit code set) or -1*/
118 static int
119 extract_msg(char* buf, int *pos, int buflen, int *exitcode, int sockfd)
120 {
121  char data[ODS_SE_MAXLINE+1], opc;
122  int datalen;
123 
124  assert(buf);
125  assert(pos);
126  assert(exitcode);
127  assert(*pos <= buflen);
128  assert(ODS_SE_MAXLINE >= buflen);
129 
130  while (1) {
131  /* Do we have a complete header? */
132  if (*pos < 3) return 0;
133  opc = buf[0];
134  datalen = (buf[1]<<8) | (buf[2]&0xFF);
135  datalen &= 0xFFFF; /* hopefully sooth tainted data checker */
136  if (datalen+3 <= *pos) {
137  /* a complete message */
138  memset(data, 0, ODS_SE_MAXLINE+1);
139  memcpy(data, buf+3, datalen);
140  *pos -= datalen+3;
141  memmove(buf, buf+datalen+3, *pos);
142 
143  if (opc == CLIENT_OPC_EXIT) {
144  fflush(stdout);
145  if (datalen != 1) return -1;
146  *exitcode = (int)buf[3];
147  return 1;
148  }
149  switch (opc) {
150  case CLIENT_OPC_STDOUT:
151  fprintf(stdout, "%s", data);
152  break;
153  case CLIENT_OPC_STDERR:
154  fprintf(stderr, "%s", data);
155  break;
156  case CLIENT_OPC_PROMPT:
157  fprintf(stdout, "%s", data);
158  fflush(stdout);
159  /* listen for input here */
160  if (!client_handleprompt(sockfd)) {
161  fprintf(stderr, "\n");
162  *exitcode = 300;
163  return 1;
164  }
165  default:
166  break;
167  }
168  continue;
169  } else if (datalen+3 > buflen) {
170  /* Message is not going to fit! Discard the data already
171  * received */
172  fprintf(stderr, "Daemon message to big, truncating.\n");
173  datalen -= *pos - 3;
174  buf[1] = datalen >> 8;
175  buf[2] = datalen & 0xFF;
176  *pos = 3;
177  return 0;
178  }
179  return 0; /* waiting for more data */
180  }
181 }
182 
191 static int
192 interface_start(const char* cmd, const char* servsock_filename)
193 {
194  struct sockaddr_un servaddr;
195  fd_set rset;
196  int sockfd, flags, exitcode = 0;
197  int ret, n, r, error = 0, inbuf_pos = 0;
198  char userbuf[ODS_SE_MAXLINE], inbuf[ODS_SE_MAXLINE];
199 
200  assert(servsock_filename);
201 
202  /* Create a socket */
203  if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
204  fprintf(stderr, "Socket creation failed: %s\n", strerror(errno));
205  return 200;
206  }
207  bzero(&servaddr, sizeof(servaddr));
208  servaddr.sun_family = AF_UNIX;
209  strncpy(servaddr.sun_path, servsock_filename, sizeof(servaddr.sun_path) - 1);
210 
211  if (connect(sockfd, (const struct sockaddr*) &servaddr, sizeof(servaddr)) == -1) {
212  if (cmd) {
213  if (strncmp(cmd, "start", 5) == 0) {
214  exitcode = system(ODS_EN_ENGINE);
215  if (exitcode == 0) {
216  close(sockfd);
217  return 0;
218  }
219  fprintf(stderr, "Error: Daemon reported a failure "
220  "starting. Please consult the logfiles.\n");
221  close(sockfd);
222  return exitcode;
223  } else if (strcmp(cmd, "running\n") == 0) {
224  fprintf(stdout, "Engine not running.\n");
225  close(sockfd);
226  return 209;
227  }
228  }
229  fprintf(stderr,
230  "Unable to connect to engine. connect() failed: "
231  "%s (\"%s\")\n", strerror(errno), servsock_filename);
232  close(sockfd);
233  return 201;
234  }
235  /* set socket to non-blocking */
236  if ((flags = fcntl(sockfd, F_GETFL, 0)) == -1) {
237  ods_log_error("[%s] unable to start interface, fcntl(F_GETFL) "
238  "failed: %s", cli_str, strerror(errno));
239  close(sockfd);
240  return 202;
241  } else if (fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) == -1) {
242  ods_log_error("[%s] unable to start interface, fcntl(F_SETFL) "
243  "failed: %s", cli_str, strerror(errno));
244  close(sockfd);
245  return 203;
246  }
247 
248  /* If we have a cmd send it to the daemon, otherwise display a
249  * prompt */
250  if (cmd) client_stdin(sockfd, cmd, strlen(cmd)+1);
251  do {
252  if (!cmd) {
253 #ifdef HAVE_READLINE
254  char *icmd_ptr;
255  if ((icmd_ptr = readline(PROMPT)) == NULL) { /* eof */
256  printf("\n");
257  break;
258  }
259  if (snprintf(userbuf, ODS_SE_MAXLINE, "%s", icmd_ptr) >= ODS_SE_MAXLINE) {
260  break;
261  }
262  free(icmd_ptr);
263  ods_str_trim(userbuf,0);
264  if (strlen(userbuf) > 0) add_history(userbuf);
265 #else
266  fprintf(stdout, "%s", PROMPT);
267  fflush(stdout);
268  n = read(fileno(stdin), userbuf, ODS_SE_MAXLINE);
269  if (n == 0) { /* eof */
270  printf("\n");
271  break;
272  } else if (n == -1) {
273  error = 205;
274  break;
275  }
276  userbuf[n] = 0;
277  ods_str_trim(userbuf,0);
278 #endif
279  /* These commands don't go through the pipe */
280  if (strcmp(userbuf, "exit") == 0 || strcmp(userbuf, "quit") == 0)
281  break;
282  /* send cmd through pipe */
283  if (!client_stdin(sockfd, userbuf, strlen(userbuf))) {
284  /* only try start on fail to send */
285  if (strcmp(userbuf, "start") == 0) {
286  if (system(ODS_EN_ENGINE) != 0) {
287  fprintf(stderr, "Error: Daemon reported a failure starting. "
288  "Please consult the logfiles.\n");
289  error = 209;
290  }
291  continue;
292  }
293  }
294  }
295 
296  while (1) {
297  /* Clean the readset and add the pipe to the daemon */
298  FD_ZERO(&rset);
299  FD_SET(sockfd, &rset);
300 
301  ret = select(sockfd+1, &rset, NULL, NULL, NULL);
302  if (ret < 0) {
303  /* *SHRUG* just some interrupt*/
304  if (errno == EINTR) continue;
305  /* anything else is an actual error */
306  perror("select()");
307  error = 204;
308  break;
309  }
310  /* Handle data coming from the daemon */
311  if (FD_ISSET(sockfd, &rset)) { /*daemon pipe is readable*/
312  n = read(sockfd, inbuf+inbuf_pos, ODS_SE_MAXLINE-inbuf_pos);
313  if (n == 0) { /* daemon closed pipe */
314  fprintf(stderr, "[Remote closed connection]\n");
315  error = 206;
316  break;
317  } else if (n == -1) { /* an error */
318  if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
319  perror("read()");
320  error = 207;
321  break;
322  }
323  inbuf_pos += n;
324  r = extract_msg(inbuf, &inbuf_pos, ODS_SE_MAXLINE, &exitcode, sockfd);
325  if (r == -1) {
326  fprintf(stderr, "Error handling message from daemon\n");
327  error = 208;
328  break;
329  } else if (r == 1) {
330  if (cmd)
331  error = exitcode;
332  else if (strlen(userbuf) != 0)
333  /* we are interactive so print response.
334  * But also suppress when no command is given. */
335  fprintf(stderr, "Command exit code: %d\n", exitcode);
336  break;
337  }
338  }
339  }
340  if (strlen(userbuf) != 0 && !strncmp(userbuf, "stop", 4))
341  break;
342  } while (error == 0 && !cmd);
343  close(sockfd);
344 
345  if ((cmd && !strncmp(cmd, "stop", 4)) ||
346  (strlen(userbuf) != 0 && !strncmp(userbuf, "stop", 4))) {
347  char line[80];
348  FILE *cmd2 = popen("pgrep ods-enforcerd","r");
349  error = 0;
350  if (fgets(line, 80, cmd2)) {
351  pid_t pid = strtoul(line, NULL, 10);
352  fprintf(stdout, "pid %d\n", pid);
353  int time = 0;
354  while (pid > 0) {
355  if(kill(pid, 0) != 0) break;
356  sleep(1);
357  if (++time>20) {
358  printf("enforcer needs more time to stop...\n");
359  time = 0;
360  }
361  }
362  }
363  }
364 
365 #ifdef HAVE_READLINE
366  clear_history();
367  rl_free_undo_list();
368 #endif
369  return error;
370 }
371 
372 int
373 main(int argc, char* argv[])
374 {
375  char* argv0;
376  char* cmd = NULL;
377  char const *socketfile = OPENDNSSEC_ENFORCER_SOCKETFILE;
378  int error, c, options_index = 0;
379  static struct option long_options[] = {
380  {"help", no_argument, 0, 'h'},
381  {"socket", required_argument, 0, 's'},
382  {"version", no_argument, 0, 'V'},
383  { 0, 0, 0, 0}
384  };
385 
386  ods_log_init("", 0, NULL, 0);
387 
388  /* Get the name of the program */
389  if((argv0 = strrchr(argv[0],'/')) == NULL)
390  argv0 = argv[0];
391  else
392  ++argv0;
393  /* parse the commandline. The + in the arg string tells getopt
394  * to stop parsing when an unknown command is found not starting
395  * with '-'. This is important for us, else switches inside commands
396  * would be consumed by getopt. */
397  while ((c=getopt_long(argc, argv, "+hVs:",
398  long_options, &options_index)) != -1) {
399  switch (c) {
400  case 'h':
401  usage(argv0, stdout);
402  exit(0);
403  case 's':
404  socketfile = optarg;
405  printf("sock set to %s\n", socketfile);
406  break;
407  case 'V':
408  version(stdout);
409  exit(0);
410  default:
411  /* unrecognized options
412  * getopt will report an error */
413  fprintf(stderr, "use --help for usage information\n");
414  exit(100);
415  }
416  }
417  argc -= optind;
418  argv += optind;
419  if (!socketfile) {
420  fprintf(stderr, "Enforcer socket file not set.\n");
421  return 101;
422  }
423  if (argc != 0)
424  cmd = ods_strcat_delim(argc, argv, ' ');
425  error = interface_start(cmd, socketfile);
426  free(cmd);
427  return error;
428 }
argv0
char * argv0
Definition: ods-migrate.c:48
main
int main(int argc, char *argv[])
Definition: ods-enforcer.c:373