/*
 * File: dpi.c
 *
 * Copyright (C) 2002-2007 Jorge Arellano Cid <jcid@dillo.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

/*
 * Dillo plugins (small programs that interact with dillo)
 *
 * Dillo plugins are designed to handle:
 *   bookmarks, cookies, FTP, downloads, files, preferences, https,
 *   datauri and a lot of any-to-html filters.
 */


#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>           /* for errno */
#include <fcntl.h>
#include <ctype.h>           /* isxdigit */

#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "../msg.h"
#include "../klist.h"
#include "IO.h"
#include "Url.h"
#include "../../dpip/dpip.h"

/* This one is tricky, some sources state it should include the byte
 * for the terminating NULL, and others say it shouldn't. */
# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
                        + strlen ((ptr)->sun_path))

/* Solaris may not have this one... */
#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif


typedef struct {
   int InTag;
   int Send2EOF;

   int DataTotalSize;
   int DataRecvSize;

   Dstr *Buf;

   int BufIdx;
   int TokIdx;
   int TokSize;
   int TokIsTag;

   ChainLink *InfoRecv;
   int Key;
} dpi_conn_t;


/*
 * Local data
 */
static Klist_t *ValidConns = NULL; /* Active connections list. It holds
                                    * pointers to dpi_conn_t structures. */
static char SharedKey[32];

/*
 * Initialize local data
 */
void a_Dpi_init(void)
{
   /* empty */
}

/*
 * Close a FD handling EINTR
 */
static void Dpi_close_fd(int fd)
{
   int st;

   dReturn_if (fd < 0);
   do
      st = close(fd);
   while (st < 0 && errno == EINTR);
}

/*
 * Create a new connection data structure
 */
static dpi_conn_t *Dpi_conn_new(ChainLink *Info)
{
   dpi_conn_t *conn = dNew0(dpi_conn_t, 1);

   conn->Buf = dStr_sized_new(8*1024);
   conn->InfoRecv = Info;
   conn->Key = a_Klist_insert(&ValidConns, conn);

   return conn;
}

/*
 * Free a connection data structure
 */
static void Dpi_conn_free(dpi_conn_t *conn)
{
   a_Klist_remove(ValidConns, conn->Key);
   dStr_free(conn->Buf, 1);
   dFree(conn);
}

/*
 * Check whether a conn is still valid.
 * Return: 1 if found, 0 otherwise
 */
static int Dpi_conn_valid(int key)
{
   return (a_Klist_get_data(ValidConns, key)) ? 1 : 0;
}

/*
 * Append the new buffer in 'dbuf' to Buf in 'conn'
 */
static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf)
{
   if (dbuf->Code == 0 && dbuf->Size > 0) {
      dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size);
   }
}

/*
 * Split the data stream into tokens.
 * Here, a token is either:
 *    a) a dpi tag
 *    b) a raw data chunk
 *
 * Return Value: 0 upon a new token, -1 on not enough data.
 *
 * TODO: define an API and move this function into libDpip.a.
*/
static int Dpi_get_token(dpi_conn_t *conn)
{
   int i, resp = -1;
   char *buf = conn->Buf->str;

   if (conn->BufIdx == conn->Buf->len) {
      dStr_truncate(conn->Buf, 0);
      conn->BufIdx = 0;
      return resp;
   }

   if (conn->Send2EOF) {
      conn->TokIdx = conn->BufIdx;
      conn->TokSize = conn->Buf->len - conn->BufIdx;
      conn->BufIdx = conn->Buf->len;
      return 0;
   }

   _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n",
        conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx);

   if (!conn->InTag) {
      /* search for start of tag */
      while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<')
         ++conn->BufIdx;
      if (conn->BufIdx < conn->Buf->len) {
         /* found */
         conn->InTag = 1;
         conn->TokIdx = conn->BufIdx;
      } else {
         MSG_ERR("[Dpi_get_token] Can't find token start\n");
      }
   }

   if (conn->InTag) {
      /* search for end of tag (EOT=" '>") */
      for (i = conn->BufIdx; i < conn->Buf->len; ++i)
         if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ')
            break;
      conn->BufIdx = i;

      if (conn->BufIdx < conn->Buf->len) {
         /* found EOT */
         conn->TokIsTag = 1;
         conn->TokSize = conn->BufIdx - conn->TokIdx + 1;
         ++conn->BufIdx;
         conn->InTag = 0;
         resp = 0;
      }
   }

   return resp;
}

/*
 * Parse a dpi tag and take the appropriate actions
 */
static void Dpi_parse_token(dpi_conn_t *conn)
{
   char *tag, *cmd, *msg, *urlstr;
   DataBuf *dbuf;
   char *Tok = conn->Buf->str + conn->TokIdx;

   if (conn->Send2EOF) {
      /* we're receiving data chunks from a HTML page */
      dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0);
      a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof");
      dFree(dbuf);
      return;
   }

   tag = dStrndup(Tok, (size_t)conn->TokSize);
   _MSG("Dpi_parse_token: {%s}\n", tag);

   cmd = a_Dpip_get_attr_l(Tok, conn->TokSize, "cmd");
   if (strcmp(cmd, "send_status_message") == 0) {
      msg = a_Dpip_get_attr_l(Tok, conn->TokSize, "msg");
      a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);
      dFree(msg);

   } else if (strcmp(cmd, "chat") == 0) {
      msg = a_Dpip_get_attr_l(Tok, conn->TokSize, "msg");
      a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);
      dFree(msg);

   } else if (strcmp(cmd, "dialog") == 0) {
      /* For now will send the dpip tag... */
      a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd);

   } else if (strcmp(cmd, "start_send_page") == 0) {
      conn->Send2EOF = 1;
      urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url");
      a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);
      dFree(urlstr);
      /* TODO: a_Dpip_get_attr_l(Tok, conn->TokSize, "send_mode") */

   } else if (strcmp(cmd, "reload_request") == 0) {
      urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url");
      a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);
      dFree(urlstr);
   }
   dFree(cmd);

   dFree(tag);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
 * Write data into a file descriptor taking care of EINTR
 * and possible data splits.
 * Return value: 1 on success, -1 on error.
 */
static int Dpi_blocking_write(int fd, const char *msg, int msg_len)
{
   int st, sent = 0;

   while (sent < msg_len) {
      st = write(fd, msg + sent, msg_len - sent);
      if (st < 0) {
         if (errno == EINTR) {
            continue;
         } else {
            MSG_ERR("[Dpi_blocking_write] %s\n", dStrerror(errno));
            break;
         }
      }
      sent += st;
   }

   return (sent == msg_len) ? 1 : -1;
}

/*
 * Read all the available data from a filedescriptor.
 * This is intended for short answers, i.e. when we know the server
 * will write it all before being preempted. For answers that may come
 * as an stream with delays, non-blocking is better.
 * Return value: read data, or NULL on error and no data.
 */
static char *Dpi_blocking_read(int fd)
{
   int st;
   const int buf_sz = 8*1024;
   char buf[buf_sz], *msg = NULL;
   Dstr *dstr = dStr_sized_new(buf_sz);

   do {
      st = read(fd, buf, buf_sz);
      if (st < 0) {
         if (errno == EINTR) {
            continue;
         } else {
            MSG_ERR("[Dpi_blocking_read] %s\n", dStrerror(errno));
            break;
         }
      } else if (st > 0) {
         dStr_append_l(dstr, buf, st);
      }
   } while (st == buf_sz);

   msg = (dstr->len > 0) ? dstr->str : NULL;
   dStr_free(dstr, (dstr->len > 0) ? FALSE : TRUE);
   return msg;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
 * Get a new data buffer (within a 'dbuf'), save it into local data,
 * split in tokens and parse the contents.
 */
static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn)
{
   DataBuf *dbuf = Data1;
   int key = conn->Key;

   /* Very useful for debugging: show the data stream as received. */
   /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */

   if (Op == IORead) {
      Dpi_append_dbuf(conn, dbuf);
      /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */
      while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) {
         Dpi_parse_token(conn);
      }

   } else if (Op == IOClose) {
      /* unused */
   }
}

/*
 * Start dpid.
 * Return: 0 starting now, 1 Error.
 */
static int Dpi_start_dpid(void)
{
   pid_t pid;
   int st_pipe[2], ret = 1;
   char *answer;

   /* create a pipe to track our child's status */
   if (pipe(st_pipe))
      return 1;

   pid = fork();
   if (pid == 0) {
      /* This is the child process.  Execute the command. */
      char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL);
      Dpi_close_fd(st_pipe[0]);
      if (execl(path1, "dpid", (char*)NULL) == -1) {
         dFree(path1);
         path1 = dStrconcat(DILLO_BINDIR, "dpid", NULL);
         if (execl(path1, "dpid", (char*)NULL) == -1) {
            dFree(path1);
            if (execlp("dpid", "dpid", (char*)NULL) == -1) {
               MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno));
               if (Dpi_blocking_write(st_pipe[1], "ERROR", 5) == -1) {
                  MSG("Dpi_start_dpid (child): can't write to pipe.\n");
               }
               Dpi_close_fd(st_pipe[1]);
               _exit (EXIT_FAILURE);
            }
         }
      }
   } else if (pid < 0) {
      /* The fork failed.  Report failure.  */
      MSG("Dpi_start_dpid: %s\n", dStrerror(errno));
      /* close the unused pipe */
      Dpi_close_fd(st_pipe[0]);
      Dpi_close_fd(st_pipe[1]);

   } else {
      /* This is the parent process, check our child status... */
      Dpi_close_fd(st_pipe[1]);
      if ((answer = Dpi_blocking_read(st_pipe[0])) != NULL) {
         MSG("Dpi_start_dpid: can't start dpid\n");
         dFree(answer);
      } else {
         ret = 0;
      }
      Dpi_close_fd(st_pipe[0]);
   }

   return ret;
}

/*
 * Read dpid's communication keys from its saved file.
 * Return value: 1 on success, -1 on error.
 */
static int Dpi_read_comm_keys(int *port)
{
   FILE *In;
   char *fname, *rcline = NULL, *tail;
   int i, ret = -1;

   fname = dStrconcat(dGethomedir(), "/.dillo/dpid_comm_keys", NULL);
   if ((In = fopen(fname, "r")) == NULL) {
      MSG_ERR("[Dpi_read_comm_keys] %s\n", dStrerror(errno));
   } else if ((rcline = dGetline(In)) == NULL) {
      MSG_ERR("[Dpi_read_comm_keys] empty file: %s\n", fname);
   } else {
      *port = strtol(rcline, &tail, 10);
      for (i = 0; *tail && isxdigit(tail[i+1]); ++i)
         SharedKey[i] = tail[i+1];
      SharedKey[i] = 0;
      ret = 1;
   }
   if (In)
      fclose(In);
   dFree(rcline);
   dFree(fname);

   return ret;
}

/*
 * Return a socket file descriptor
 */
static int Dpi_make_socket_fd()
{
   int fd, one = 1, ret = -1;

   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
      /* avoid delays when sending small pieces of data */
      setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
      ret = fd;
   }
   return ret;
}

/*
 * Make a connection test for a IDS.
 * Return: 1 OK, -1 Not working.
 */
static int Dpi_check_dpid_ids()
{
   struct sockaddr_in sin;
   const socklen_t sin_sz = sizeof(sin);
   int sock_fd, dpid_port, ret = -1;

   /* socket connection test */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

   if (Dpi_read_comm_keys(&dpid_port) != -1) {
      sin.sin_port = htons(dpid_port);
      if ((sock_fd = Dpi_make_socket_fd()) == -1) {
         MSG("Dpi_check_dpid_ids: sock_fd=%d %s\n", sock_fd, dStrerror(errno));
      } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {
         MSG("Dpi_check_dpid_ids: %s\n", dStrerror(errno));
      } else {
         Dpi_close_fd(sock_fd);
         ret = 1;
      }
   }
   return ret;
}

/*
 * Confirm that the dpid is running. If not, start it.
 * Return: 0 running OK, 1 starting (EAGAIN), 2 Error.
 */
static int Dpi_check_dpid(int num_tries)
{
   static int starting = 0;
   int check_st = 1, ret = 2;

   check_st = Dpi_check_dpid_ids();
   _MSG("Dpi_check_dpid: check_st=%d\n", check_st);

   if (check_st == 1) {
      /* connection test with dpi server passed */
      starting = 0;
      ret = 0;
   } else {
      if (!starting) {
         /* start dpid */
         if (Dpi_start_dpid() == 0) {
            starting = 1;
            ret = 1;
         }
      } else if (++starting < num_tries) {
         /* starting */
         ret = 1;
      } else {
         /* we waited too much, report an error... */
         starting = 0;
      }
   }

   _MSG("Dpi_check_dpid:: %s\n",
        (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR"));
   return ret;
}

/*
 * Confirm that the dpid is running. If not, start it.
 * Return: 0 running OK, 2 Error.
 */
static int Dpi_blocking_start_dpid(void)
{
   int cst, try = 0,
       n_tries = 12; /* 3 seconds */

   /* test the dpid, and wait a bit for it to start if necessary */
   while ((cst = Dpi_check_dpid(n_tries)) == 1) {
      MSG("Dpi_blocking_start_dpid: try %d\n", ++try);
      usleep(250000); /* 1/4 sec */
   }
   return cst;
}

/*
 * Return the dpi server's port number, or -1 on error.
 * (A query is sent to dpid and then its answer parsed)
 * note: as the available servers and/or the dpi socket directory can
 *       change at any time, we'll ask each time. If someday we find
 *       that connecting each time significantly degrades performance,
 *       an optimized approach can be tried.
 */
static int Dpi_get_server_port(const char *server_name)
{
   int sock_fd = -1, dpi_port = -1;
   int dpid_port, ok = 0;
   struct sockaddr_in sin;
   char *cmd, *request, *rply = NULL, *port_str;
   socklen_t sin_sz;

   dReturn_val_if_fail (server_name != NULL, dpi_port);
   _MSG("Dpi_get_server_port:: server_name = [%s]\n", server_name);

   /* Read dpid's port from saved file */
   if (Dpi_read_comm_keys(&dpid_port) != -1) {
      ok = 1;
   }
   if (ok) {
      /* Connect a socket with dpid */
      ok = 0;
      sin_sz = sizeof(sin);
      memset(&sin, 0, sizeof(sin));
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      sin.sin_port = htons(dpid_port);
      if ((sock_fd = Dpi_make_socket_fd()) == -1 ||
          connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {
         MSG("Dpi_get_server_port: %s\n", dStrerror(errno));
      } else {
         ok = 1;
      }
   }
   if (ok) {
      /* ask dpid to check the dpi and send its port number back */
      ok = 0;
      request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name);
      _MSG("[%s]\n", request);

      if (Dpi_blocking_write(sock_fd, request, strlen(request)) == -1) {
         MSG("Dpi_get_server_port: %s\n", dStrerror(errno));
      } else {
         ok = 1;
      }
      dFree(request);
   }
   if (ok) {
      /* Get the reply */
      ok = 0;
      if ((rply = Dpi_blocking_read(sock_fd)) == NULL) {
         MSG("Dpi_get_server_port: can't read server port from dpid.\n");
      } else {
         ok = 1;
      }
   }
   if (ok) {
      /* Parse reply */
      ok = 0;
      cmd = a_Dpip_get_attr(rply, "cmd");
      if (strcmp(cmd, "send_data") == 0) {
         port_str = a_Dpip_get_attr(rply, "msg");
         _MSG("Dpi_get_server_port: rply=%s\n", rply);
         _MSG("Dpi_get_server_port: port_str=%s\n", port_str);
         dpi_port = strtol(port_str, NULL, 10);
         dFree(port_str);
         ok = 1;
      }
      dFree(cmd);
   }
   dFree(rply);
   Dpi_close_fd(sock_fd);

   return ok ? dpi_port : -1;
}


/*
 * Connect a socket to a dpi server and return the socket's FD.
 * We have to ask 'dpid' (dpi daemon) for the port of the target dpi server.
 * Once we have it, then the proper file descriptor is returned (-1 on error).
 */
static int Dpi_connect_socket(const char *server_name, int retry)
{
   struct sockaddr_in sin;
   int sock_fd, err, dpi_port, ret=-1;
   char *cmd = NULL;

   /* Query dpid for the port number for this server */
   if ((dpi_port = Dpi_get_server_port(server_name)) == -1) {
      _MSG("Dpi_connect_socket:: can't get port number for %s\n", server_name);
      return -1;
   }
   _MSG("Dpi_connect_socket: server=%s port=%d\n", server_name, dpi_port);

   /* connect with this server's socket */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   sin.sin_port = htons(dpi_port);

   if ((sock_fd = Dpi_make_socket_fd()) == -1) {
      perror("[dpi::socket]");
   } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) {
      err = errno;
      sock_fd = -1;
      MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno));
      if (retry) {
         switch (err) {
            case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL:
               sock_fd = Dpi_connect_socket(server_name, FALSE);
               break;
         }
      }

   /* send authentication Key (the server closes sock_fd on error) */
   } else if (!(cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "auth", SharedKey))) {
      MSG_ERR("[Dpi_connect_socket] Can't make auth message.\n");
   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {
      MSG_ERR("[Dpi_connect_socket] Can't send auth message.\n");
   } else {
      ret = sock_fd;
   }
   dFree(cmd);

   return ret;
}

/*
 * CCC function for the Dpi module
 */
void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
               void *Data1, void *Data2)
{
   dpi_conn_t *conn;
   int SockFD = -1, st;

   dReturn_if_fail( a_Chain_check("a_Dpi_ccc", Op, Branch, Dir, Info) );

   if (Branch == 1) {
      if (Dir == BCK) {
         /* Send commands to dpi-server */
         switch (Op) {
         case OpStart:
            if ((st = Dpi_blocking_start_dpid()) == 0) {
               SockFD = Dpi_connect_socket(Data1, TRUE);
               if (SockFD != -1) {
                  int *fd = dNew(int, 1);
                  *fd = SockFD;
                  Info->LocalKey = fd;
                  a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1);
                  a_Chain_bcb(OpStart, Info, NULL, NULL);
               }
            }

            if (st == 0 && SockFD != -1) {
               a_Chain_bcb(OpSend, Info, &SockFD, "FD");
               a_Chain_fcb(OpSend, Info, &SockFD, "FD");
               a_Chain_fcb(OpSend, Info, NULL, "DpidOK");
            } else {
               MSG_ERR("dpi.c: can't start dpi daemon\n");
               a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR");
            }
            break;
         case OpSend:
            a_Chain_bcb(OpSend, Info, Data1, NULL);
            break;
         case OpEnd:
            a_Chain_bcb(OpEnd, Info, NULL, NULL);
            dFree(Info->LocalKey);
            dFree(Info);
            break;
         case OpAbort:
            a_Chain_bcb(OpAbort, Info, NULL, NULL);
            dFree(Info->LocalKey);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      } else {  /* 1 FWD */
         /* Send commands to dpi-server (status) */
         switch (Op) {
         case OpAbort:
            a_Chain_fcb(OpAbort, Info, NULL, Data2);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      }

   } else if (Branch == 2) {
      if (Dir == FWD) {
         /* Receiving from server */
         switch (Op) {
         case OpSend:
            /* Data1 = dbuf */
            Dpi_process_dbuf(IORead, Data1, Info->LocalKey);
            break;
         case OpEnd:
            a_Chain_fcb(OpEnd, Info, NULL, NULL);
            Dpi_conn_free(Info->LocalKey);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      } else {  /* 2 BCK */
         switch (Op) {
         case OpStart:
            conn = Dpi_conn_new(Info);
            Info->LocalKey = conn;

            /* Hack: for receiving HTTP through the DPI framework */
            if (strcmp(Data2, "http") == 0) {
               conn->Send2EOF = 1;
            }

            a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2);
            a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */
            break;
         case OpSend:
            if (Data2 && !strcmp(Data2, "FD")) {
               a_Chain_bcb(OpSend, Info, Data1, Data2);
            }
            break;
         case OpAbort:
            a_Chain_bcb(OpAbort, Info, NULL, NULL);
            Dpi_conn_free(Info->LocalKey);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      }
   }
}

/*! Let dpid know dillo is no longer running.
 * Note: currently disabled. It may serve to let the cookies dpi know
 * when to expire session cookies.
 */
void a_Dpi_dillo_exit()
{

}

/*
 * Send a command to a dpi server, and block until the answer is got.
 * Return value: the dpip tag answer as an string, NULL on error.
 */
char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd)
{
   int cst, sock_fd;
   char *ret = NULL;

   /* test the dpid, and wait a bit for it to start if necessary */
   if ((cst = Dpi_blocking_start_dpid()) != 0) {
      return ret;
   }

   if ((sock_fd = Dpi_connect_socket(server_name, TRUE)) == -1) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't connect to server.\n");
   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't send message.\n");
   } if ((ret = Dpi_blocking_read(sock_fd)) == NULL) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't read message.\n");
   }
   Dpi_close_fd(sock_fd);

   return ret;
}

