[программка] HiddenPhone - голосовые сообщения через .onion

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ББ on 2018-05-01 13 ч.

[программка] HiddenPhone - голосовые сообщения через .onion

Всем привет, пришло время опубликовать еще одну самодельную программу.

HiddenPhone

Получилось что-то вроде рации Zello, только попроще и более приспособленное под onion.

Особенности программы:

  • Без использования UDP, встроенная поддержка SOCKSv4, полностью готова к работе с Tor

  • Управление через горячую клавишу.

  • Написана на языке Си, легковесная, быстрая

  • Использует библиотеку Xlib для контроля горячей клавиши и библиотеку libasound для доступа к звуковой карте, поэтому работает только на Linux, для запуска на Windows потребуется глубокая переделка.

  • Звуковое сообщение всегда доставляется целиком, воспроизведение не начнется, пока сообщение не будет записано и полностью доставлено. Такой подход позволяет программе надежно работать на самых медленных соединениях. В отличие от других мессенджеров программа не пытается сократить задержку звука до минимума в ущерб качеству.

  • В качестве кодека используется MP2 из библиотеки libavcodec

  • Программа реализует соединение типа точка - точка. Подсоединение третьего абонента возможно, но при этом предыдущее соединение разрывается.

  • На принимающей стороне звучат звуковые сигналы начала и конца сообщения

  • Можно включить запись входящих сообщений (опция -mp2save)

  • У программы есть русскоязычная поддержка в лице меня :)

Код

Три файла
hp.c

// Файл hp.c
// Программа HiddenPhone впервые публикуется на форуме Runion https://lwplxqzvmgu43uff.onion.tor.my
// Рядом с файлом hp.c должны лежать файлы hp_audio.c и hp_key.c
// Компилировать командой:
// gcc -Wall hphone1.c -lasound -lavcodec -lavutil -lX11 -o hphone1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>

#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "libavcodec/avcodec.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/samplefmt.h"

#include "../include/alsa/asoundlib.h"

#define TOR_PORT_FIRST 9150
#define TOR_PORT_SECOND 9050

uint16_t onion_target_port = 0;
uint16_t bind_port = 0;
uint16_t clearnet_port = 0;
uint16_t tor_port = TOR_PORT_FIRST; // Программа автоматически пробует оба порта
  // Предпочтение отдается порту Tor-Browser
char *oniondomain = "";
char *clearnet_ip = "";

int server_socket_fd = 0;
int connection_socket_fd = 0;
int intro_socket_fd = 0;
int max_fd;
int connection_works = 0;
int intro_works = 0;

int noheartbeat_count = 0;

int xlib_pid = -1;

enum {BUFFER_STATE_READY, BUFFER_STATE_PARTIAL, BUFFER_STATE_ERROR_FD_CLOSED};

// очередь на отправку
struct upload_page_header {void *nextpage; long int datasize;};

long int upload_queue_bytes_in_use = 0;
uint8_t *upload_queue_first = NULL;
uint8_t *upload_queue_last = NULL;
long int upload_queue_p = 0;

uint8_t intro_buffer [2048];
long int intro_buffer_p = 0;

uint8_t download_header_buffer [16];
long int download_header_buffer_p = 0;

int disablebutton = 0;
int enableheartbeat = 0;
int temporary_enableheartbeat = 0;

int normalize = 0;
int mp2save = 0;

int dont_close_read_device = 0;
snd_pcm_t *recorddevice_handle = NULL;

struct parsed_header_struct {int signature_ok;
                             uint32_t bodysize;
                             uint32_t msg_type;
                             uint32_t additional_size;} parsed_header;

uint8_t *download_body_buffer = NULL;
long int download_body_buffer_size = 0;
long int download_body_additional_info_size = 0;
uint32_t download_message_type;
long int download_body_buffer_p = 0;

unsigned long int raw_audio_bytes_in_use = 0;

enum {CONNECT, BIND, NOPE} mode = NOPE;
enum {TOR, CLEARNET} clearnet = TOR;

// типы сообщений
#define HIDDENPHONE_MSG_AUTH_PASSWORD 2
#define HIDDENPHONE_MSG_PASSWORD_OK 10
#define HIDDENPHONE_MSG_SOUND 30
#define HIDDENPHONE_MSG_SOUND_ARRIVED 40 // не используется
#define HIDDENPHONE_MSG_HEARTBEAT_PLEASE 75
#define HIDDENPHONE_MSG_HEARTBEAT 76
#define HIDDENPHONE_MSG_HEARTBEAT_OK 77
#define HIDDENPHONE_MSG_HEARTBEAT_ENABLE 78

char *password = "";

int disable_socksproxy = 0;

struct audio_page_struct {void *next_page; int page_size; void *samples; int samples_size;};

void print_error_and_exit(char * str) {
  fprintf(stderr, "Error: %s\n", str);
  if (xlib_pid != -1) kill(xlib_pid, SIGTERM);
  exit (1);
};

int hphone_try_to_read (int fd, uint8_t *buffer, long int *buffer_p, long int buffer_size) {
  long int read_count_needed = buffer_size - (*buffer_p);
  int ret = read(fd, buffer + (*buffer_p), read_count_needed);
  if (ret == -1) {
    perror("read");
    fprintf(stderr, "Ошибка чтения, закрываю дескриптор\n");
    close(fd);
    return BUFFER_STATE_ERROR_FD_CLOSED;
  };
  if (ret == 0) {
    fprintf(stderr, "Соединение закрылось\n");
    close(fd);
    return BUFFER_STATE_ERROR_FD_CLOSED;
  };
  if (ret > 0) {
    (*buffer_p) += ret;
    if ((*buffer_p) == buffer_size) return BUFFER_STATE_READY;
  };
  return BUFFER_STATE_PARTIAL;
};


/* Функция ставит страницу в очередь на отправку, если к странице
   привязаны еще страницы, то они тоже ставятся */

void insert_in_upload_queue (void *page) {
  struct upload_page_header *lastpage_header;
  struct upload_page_header *new_last_page_header = page;
  if (!upload_queue_first) upload_queue_first = page;
  if (upload_queue_last) {
    lastpage_header = (struct upload_page_header*) upload_queue_last;
    lastpage_header->nextpage = page;
  };

  // Нужно найти последнюю страницу и записать ее адрес в upload_queue_last
  while (new_last_page_header->nextpage) new_last_page_header = new_last_page_header->nextpage;
  upload_queue_last = (uint8_t *) new_last_page_header;
};

void connection_buffers_reset () {
  connection_works = 0;
  download_header_buffer_p = 0;
  if (download_body_buffer) free(download_body_buffer);
  download_body_buffer = NULL;
  download_body_buffer_size = 0;
  download_body_buffer_p = 0;

  // этот код отчищает очередь отправки
  void *nextpage;
  struct upload_page_header *header;
  while (upload_queue_first) {
    header = (struct upload_page_header *) upload_queue_first;
    upload_queue_bytes_in_use -= sizeof(struct upload_page_header) + header->datasize;
    nextpage = header->nextpage;
    free(upload_queue_first);
    upload_queue_first = nextpage;
  };
  upload_queue_last = NULL;
  upload_queue_p = 0;
  if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");
};

void send_one_upload_page (void) {
  if (!upload_queue_first) print_error_and_exit("Нет страниц для отправки");
  struct upload_page_header *header = (struct upload_page_header *) upload_queue_first;
  uint8_t *pagedata = upload_queue_first + sizeof(struct upload_page_header);
  long int need_to_send = header->datasize - upload_queue_p;
  if (need_to_send == 0) print_error_and_exit("Эту страницу мы уже отправили");
  int send_flags = MSG_NOSIGNAL | MSG_DONTWAIT | ((header->nextpage) ? MSG_MORE : 0);
  int ret = send(connection_socket_fd, pagedata + upload_queue_p, need_to_send, send_flags);
  if (ret > 0) upload_queue_p += ret;
  if (ret == 0) print_error_and_exit("send вернул 0");
  if (ret == -1) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      fprintf(stderr, "Буфер отправления переполнился\n");
    } else {
      perror("send");
      fprintf(stderr, "Ошибка соединения, через некоторое время откроем снова\n");
      close(connection_socket_fd);
      connection_buffers_reset();
    };
  };
  if (ret == need_to_send) {
    //fprintf(stderr, "Отправлена страница %x\n", (unsigned int)upload_queue_first);
    // страница отправлена, теперь нужно освободить память
    void *prev_page = upload_queue_first;
    upload_queue_bytes_in_use -= sizeof(struct upload_page_header) + header->datasize;
    upload_queue_first = header->nextpage;
    //fprintf(stderr, "Следующая страница %x\n", (unsigned int)upload_queue_first);
    free(prev_page);
    prev_page = NULL;
    upload_queue_p = 0;
    if (upload_queue_first == NULL) upload_queue_last = NULL;
    if (upload_queue_first == NULL) fprintf(stderr, "Сообщение ушло, очередь отправки пуста\n");
    if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");
  };
};

uint32_t hphone_get_value (uint8_t *pointer) {
  uint32_t lo = ntohl(*((uint32_t *)pointer));
  return lo;
};

// эта функция заполняет поля структуры parsed_header
void hphone_parse_header(uint8_t *pointer) {
  parsed_header.signature_ok = (strncmp((char *)pointer, "HPHO", 4) == 0) ? 1 : 0;
  parsed_header.bodysize = hphone_get_value(pointer+4);
  parsed_header.msg_type = hphone_get_value(pointer+8);
  parsed_header.additional_size = hphone_get_value(pointer+12);
};

void list_options () {
  print_error_and_exit("Неправильно задана опция, вот список опций:\n\n"
"-bind PORT\n-bindclearnet PORT    - открыть порт на localhost или на всех интерфейсах\n"
"-tor ONIONADDRESS PORT - подключиться к onion адресу через Тор\n"
"-clearnet IPADDERSS PORT  - подключиться напрямую к порту\n"
"-pass PASSWORD        - пароль для проверки\n"
"-pd PLAYBACKDEVICE\n"
"-rd RECORDDEVICE      - выбор аудиокарты, напр. plughw:1\n"
"-dontclose            - не закрывать карту после записи. Помогает, если карта медленно включается\n"
"-mp2save              - создавать mp2 файлы с входящими сообщениями\n"
"-heartbeat            - посылать heartbeat сообщения для проверки соединения\n"
"-normalize            - пока не реализованная функция,\n"
" автоматически поднимает громкость сообщения перед отправкой\n"
"-nobutton             - выключить кнопку\n");

};

void * hphone_make_upload_page (long int size) {
  void *page = malloc(sizeof(struct upload_page_header) + size);
  upload_queue_bytes_in_use += sizeof(struct upload_page_header) + size;
  if (!page) print_error_and_exit("Память не выделяется");
  struct upload_page_header *header = page;
  header->datasize = size;
  header->nextpage = NULL;
  return page;
};

void hphone_send_header (uint32_t body_size, uint32_t msg_type, uint32_t info_size) {
  uint8_t *page = hphone_make_upload_page(16);
  uint8_t *pagedata = page+sizeof(struct upload_page_header);
  strcpy((char *)pagedata, "HPHO");
  *((uint32_t *)(pagedata+4)) = htonl(body_size);
  *((uint32_t *)(pagedata+8)) = htonl(msg_type);
  *((uint32_t *)(pagedata+12)) = htonl(info_size);

  // Кусок памяти заполнен, теперь его надо поставить в очередь на отправку
  insert_in_upload_queue(page);
};

void reconnect_socket (void) {
  if (connection_works) {
    fprintf(stderr, "Старое соединение закрывается\n");
    close(connection_socket_fd);
    connection_buffers_reset();
  };
  if (mode != CONNECT) print_error_and_exit("Ошибка режима");
  connection_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in connect_sockaddr_in;
  connect_sockaddr_in.sin_family = AF_INET;
  int ret;
  if (clearnet == TOR) {
    connect_sockaddr_in.sin_port = htons(tor_port);
    ret = inet_aton("127.0.0.1", &connect_sockaddr_in.sin_addr);
  };
  if (clearnet == CLEARNET) {
    fprintf(stderr, "Осторожно CLEARNET, шифрование выключено\n");
    connect_sockaddr_in.sin_port = htons(clearnet_port);
    ret = inet_aton(clearnet_ip, &connect_sockaddr_in.sin_addr);
  };
  if (!ret) print_error_and_exit("IP не валидный");
  fprintf(stderr, "Подсоединяюсь к порту %i, по адресу %s\n", ntohs(connect_sockaddr_in.sin_port),
       inet_ntoa(connect_sockaddr_in.sin_addr));
  ret = connect(connection_socket_fd, (struct sockaddr *) &connect_sockaddr_in, sizeof (connect_sockaddr_in));
  if (ret == -1) {
    if (errno == ECONNREFUSED) {
       fprintf(stderr, "Соединение сброшено, будет повторная попытка\n");
       close(connection_socket_fd);

       if (clearnet == TOR && tor_port == TOR_PORT_FIRST) {
         tor_port = TOR_PORT_SECOND; // если на 9150 не получилось, сразу же пробуем на 9050
         reconnect_socket();
         return;
       };
    } else if (errno == EINPROGRESS) {
      fprintf(stderr, "Асинхронное открытие.\n");
      connection_works = 1;
    } else {
      perror("connect");
      fprintf(stderr, "Ошибка при открытии соединения\n");
    }
  } else {
    connection_works = 1;
    fprintf(stderr, "Соединение принято\n");
  }

  uint16_t socks4a_port;
  char socks4a_header[] = "\x04\x01\x00\x50\x00\x00\x00\x01user";
  uint8_t socks_response[9];
  uint8_t *response_byte;
  int oniondomain_length = strlen(oniondomain)+1;
  if (connection_works) {
    if (clearnet == TOR) {
      fprintf(stderr, "Посылаю заголовок SOCKSv4a\n");
      socks4a_port = htons(onion_target_port);
      memcpy(socks4a_header+2, &socks4a_port, 2);
      ret = send(connection_socket_fd, socks4a_header, sizeof(socks4a_header), MSG_MORE);
      if (ret < sizeof(socks4a_header)) print_error_and_exit("Не отправляется");
      ret = send(connection_socket_fd, oniondomain, oniondomain_length, 0);
      if (ret < oniondomain_length) print_error_and_exit("Слишком длинный адрес");
      memset(socks_response, 0, 9);
      ret = recv(connection_socket_fd, socks_response, 8, MSG_WAITALL);
      if (ret < 8) print_error_and_exit("Ответ не читается");
      response_byte = socks_response+1;
      switch(*response_byte) {
        case 0x5A: fprintf(stderr, "Запрос принят\n");
        break;
        case 0x5B: print_error_and_exit("SOCKS запрос отклонен");
        break;
        default: print_error_and_exit("SOCKS непонятный ответ");
      };
    };
    fprintf(stderr, "Посылаю 2048 байт с паролем\n");
    hphone_send_header(2048 - 16, HIDDENPHONE_MSG_AUTH_PASSWORD, 0);
    void *intro_page = hphone_make_upload_page (2048 - 16);
    void *intro_page_data = intro_page + sizeof(struct upload_page_header);
    memset(intro_page_data, '\0', 2048 - 16);
    strncpy((char *)intro_page_data, password, 2048-50);
    insert_in_upload_queue(intro_page);
    if (enableheartbeat) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_ENABLE,0);
  };
};

#include "hp_key.c"
#include "hp_audio.c"

int main (int argc, char **argv) {
  int ret, i;
  int buffer_state;

  void *decoded_sound = NULL;

  char *record_device_name = "default";
  char *playback_device_name = "default";

  void *encoded_sound_first_page = NULL;
  long int encoded_sound_size = 0;

  avcodec_register_all();

  // парсим опции
  for (i = 1; i < argc; i++) {
    if (strncmp(argv[i], "-bind", 5) == 0) {
      if (!argv[i+1]) list_options();
      if (atol(argv[i+1]) > 65535) print_error_and_exit("Номер порта слишком большой");
      bind_port = atol(argv[i+1]);
      if (bind_port <= 0) print_error_and_exit("Недопустимый номер");
      mode = BIND;
      if (strcmp(argv[i], "-bindclearnet") == 0) clearnet = CLEARNET;
      i++;
      continue;
    };
    if (strcmp(argv[i], "-pass") == 0) {
      if (!argv[i+1]) list_options();
      password = argv[i+1];
      i++;
      continue;
    };
    if (strcmp(argv[i], "-clearnet") == 0) {
      if (!argv[i+1]) list_options();
      if (!argv[i+2]) list_options();
      clearnet_ip = argv[i+1];
      if (atol(argv[i+2]) > 65535) print_error_and_exit("Номер порта слишком большой");
      clearnet_port = atol(argv[i+2]);
      if (clearnet_port <= 0) print_error_and_exit("Недопустимый номер");
      mode = CONNECT;
      clearnet = CLEARNET;
      i+=2;
      continue;
    };
    if (strcmp(argv[i], "-tor") == 0) {
      if (!argv[i+1]) list_options();
      if (!argv[i+2]) list_options();
      oniondomain = argv[i+1];
      if (atol(argv[i+2]) > 65535) print_error_and_exit("Номер порта слишком большой");
      onion_target_port = atol(argv[i+2]);
      mode = CONNECT;
      clearnet = TOR;
      i+=2;
      continue;
    };
    if (strcmp(argv[i], "-rd") == 0) {
      if (!argv[i+1]) list_options();
      record_device_name = argv[i+1];
      i++;
      continue;
    };
    if (strcmp(argv[i], "-pd") == 0) {
      if (!argv[i+1]) list_options();
      playback_device_name = argv[i+1];
      i++;
      continue;
    };

    if (strcmp(argv[i], "-dontclose") == 0) {
      dont_close_read_device = 1;
      continue;
    };
    if (strcmp(argv[i], "-nobutton") == 0) {
      disablebutton = 1;
      continue;
    };
    if (strcmp(argv[i], "-heartbeat") == 0) {
      enableheartbeat = 1;
      continue;
    };
    if (strcmp(argv[i], "-normalize") == 0) {
      normalize = 1;
      continue;
    };
    if (strcmp(argv[i], "-mp2save") == 0) {
      mp2save = 1;
      continue;
    };

    fprintf(stderr, "Неправильная опция %s\n", argv[i]);
    list_options();
  };
  


  // ставим ловушку на клавишу в Xlib
  int xlib_pipe[2];
  if (pipe(xlib_pipe) != 0) print_error_and_exit("Пайп не открывается");
  if (!disablebutton) {
    xlib_pid = fork();
    if (xlib_pid == 0) {
      hphone_monitor_key(xlib_pipe[1]);
      exit(0); // Заканчиваю дочерний процесс
    } else fprintf(stderr, "Дочерний процесс %i мониторит кнопку\n", xlib_pid);
  };


  struct sockaddr_in local_sockaddr_in;
  if (mode == BIND) {
    local_sockaddr_in.sin_family = AF_INET;
    local_sockaddr_in.sin_port = htons(bind_port);
    if (clearnet == TOR) {
      ret = inet_aton("127.0.0.1", &local_sockaddr_in.sin_addr);
      if (!ret) print_error_and_exit("IP не валидный");
    } else if (clearnet == CLEARNET) {
      fprintf(stderr, "Осторожно CLEARNET, порт будет виден с соседних компьютеров\n");
      local_sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
    };
    server_socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    fprintf(stderr, "Открываю порт %i, на адресе %s\n", ntohs(local_sockaddr_in.sin_port),
         inet_ntoa(local_sockaddr_in.sin_addr));
    ret = bind(server_socket_fd, (struct sockaddr *) &local_sockaddr_in, sizeof(local_sockaddr_in));
    if (ret) {
      perror("bind");
      print_error_and_exit("Порт не привязывается");
    };
    ret = listen(server_socket_fd, 5);
    if (ret) {
      perror("listen");
      print_error_and_exit("Порт не открывается на прием");
    };
  };

  if (mode == CONNECT) {
    reconnect_socket();
  };

  if (mode == NOPE) fprintf(stderr, "Вы не задали режим работы, соединения не будет.\n"
   "Используйте опции -tor или -bind\n");

  fd_set read_fd_kit, write_fd_kit;
  struct timeval time_value;
  char c = ' ';

  while (1) {
    // главный цикл
    FD_ZERO(&read_fd_kit);
    FD_ZERO(&write_fd_kit);
    FD_SET(xlib_pipe[0], &read_fd_kit);
    if (connection_works) FD_SET(connection_socket_fd, &read_fd_kit);
    if (mode == BIND) FD_SET(server_socket_fd, &read_fd_kit);
    if (intro_works) FD_SET(intro_socket_fd, &read_fd_kit);
    if (upload_queue_first && connection_works) FD_SET(connection_socket_fd, &write_fd_kit);

    max_fd = xlib_pipe[0];
    if (connection_socket_fd > max_fd) max_fd = connection_socket_fd;
    if (server_socket_fd > max_fd) max_fd = server_socket_fd;
    if (intro_socket_fd > max_fd) max_fd = intro_socket_fd;

    time_value.tv_sec = (mode == BIND) ? 40 : 60;
    time_value.tv_usec = 0;

    //fprintf(stderr, "Захожу в select\n");
    ret = select(max_fd+1, &read_fd_kit, &write_fd_kit, NULL, &time_value);
    if (ret == -1) {
      perror("select()");
      print_error_and_exit("Ошибка с дескрипторами");
    };

    if (ret == 0 && temporary_enableheartbeat) {
      if (mode == BIND && connection_works) {
        fprintf(stderr, "Посылаю heartbeat\n");
        hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT, 0);
      };
      if (mode == CONNECT) {
        noheartbeat_count++;
        if (noheartbeat_count == 3 && connection_works) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_PLEASE, 0);
        if (noheartbeat_count > 5) {
          noheartbeat_count = 0;
          reconnect_socket();
        };
      };
    };

    if (mode == CONNECT && !connection_works) {
      if (tor_port == TOR_PORT_SECOND) tor_port = TOR_PORT_FIRST;
      reconnect_socket();
    }

    if (intro_works && FD_ISSET(intro_socket_fd, &read_fd_kit)) {
      fprintf(stderr, "На пригласительный сокет пришла порция данных\n");
      buffer_state = hphone_try_to_read(intro_socket_fd, intro_buffer, &intro_buffer_p, sizeof(intro_buffer));
      if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
        intro_works = 0;
        intro_buffer_p = 0;
      };
      
      if (buffer_state == BUFFER_STATE_READY) {
        // проверка пароля
        intro_buffer[2047] = '\0';
        hphone_parse_header(intro_buffer);
        
        if (parsed_header.signature_ok &&
            parsed_header.bodysize == 2048 - 16 &&
            parsed_header.msg_type == HIDDENPHONE_MSG_AUTH_PASSWORD &&
            parsed_header.additional_size == 0 &&
            strcmp((char *)intro_buffer+16, password) == 0 ) {
          fprintf(stderr, "Пароль правильный\n");
          if (connection_works) {
            fprintf(stderr, "Закрываю старое звуковое соединение, начинаю использовать новое\n");
            connection_buffers_reset();
            ret = close(connection_socket_fd);
            if (ret) fprintf(stderr, "Предыдущий звуковой сокет при закрытии сообщил о какой-то фигне\n");
          };
          connection_socket_fd = intro_socket_fd;
          intro_works = 0;
          connection_works = 1;
          hphone_send_header(0, HIDDENPHONE_MSG_PASSWORD_OK, 0);
          temporary_enableheartbeat = enableheartbeat;
          if (enableheartbeat) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_ENABLE,0);
          continue; // Нужно заново запустить select
        } else {
          fprintf(stderr, "Проверка не пройдена, либо пароль неправильный, либо протокол чужой\n");
          intro_works = 0;
          intro_buffer_p = 0;
          close(intro_socket_fd);
        };
      };
    };

    if (mode == BIND && FD_ISSET(server_socket_fd, &read_fd_kit)) {
      fprintf(stderr, "Входящее соединение, принимаю на пригласительный сокет\n");
      if (intro_works) {
        fprintf(stderr, "Предыдущий пригласительный сокет не отработал, заменяю на этот\n");
        ret = close(intro_socket_fd);
        if (ret) fprintf(stderr, "Предыдущий пригласительный сокет при закрытии сообщил о какой-то фигне\n");
        intro_works = 0;
        intro_buffer_p = 0;
      };
      intro_socket_fd = accept(server_socket_fd, NULL, NULL);
      if (intro_socket_fd == -1) fprintf(stderr, "Соединение не удалось\n");
      else {
        intro_works = 1;
        intro_buffer_p = 0;
      };
    };

    if (connection_works && FD_ISSET(connection_socket_fd, &read_fd_kit)) {
      //fprintf(stderr, "select чтение на звуковом сокете\n");
      if (download_header_buffer_p < sizeof(download_header_buffer)) {
        // читаем заголовок
        buffer_state = hphone_try_to_read(connection_socket_fd, download_header_buffer,
               &download_header_buffer_p, sizeof(download_header_buffer));
        if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
          connection_buffers_reset();
        };
        if (buffer_state == BUFFER_STATE_READY) {
          hphone_parse_header(download_header_buffer);
          if (!parsed_header.signature_ok) print_error_and_exit("Сигнатура не сходится");
          switch (parsed_header.msg_type) {
            case HIDDENPHONE_MSG_PASSWORD_OK:
            fprintf(stderr, "Пароль подошел\n");
            break;
            case HIDDENPHONE_MSG_SOUND:
            fprintf(stderr, "На подходе звуковое сообщение, длина %li байт\n", (long int) parsed_header.bodysize);
            break;
            case HIDDENPHONE_MSG_SOUND_ARRIVED:
            fprintf(stderr, "Звук доставлен\n");
            break;
            case HIDDENPHONE_MSG_HEARTBEAT:
            fprintf(stderr, "Пришел heartbeat, отвечаю\n");
            noheartbeat_count = 0;
            hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_OK, 0);
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_OK:
            fprintf(stderr, "Пришел ответ на heartbeat\n");
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_PLEASE:
            fprintf(stderr, "Клиент просит отправить heartbeat, отправляю\n");
            hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT, 0);
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_ENABLE:
            fprintf(stderr, "Другой конец попросил включить heartbeat\n");
            temporary_enableheartbeat = 1;
            break;
            default:
            fprintf(stderr, "Неизвестный тип сообщения %li\n", (long int) parsed_header.msg_type);
            break;
          };

          if (parsed_header.bodysize > 0) {
            if (download_body_buffer) print_error_and_exit("Буфер не освобожден");
            download_body_buffer = malloc(parsed_header.bodysize + (FF_INPUT_BUFFER_PADDING_SIZE * 2));
            if (!download_body_buffer) print_error_and_exit("Память не выделяется");
            download_body_buffer_size = parsed_header.bodysize;
            download_body_additional_info_size = parsed_header.additional_size;
            download_message_type = parsed_header.msg_type;
            download_body_buffer_p = 0;
          } else {
            download_header_buffer_p = 0; // буфер готов к следующему заголовку
          };
        };
      } else {
        // читаем тело сообщения
        if (!download_body_buffer) print_error_and_exit("Буфера нет");
        buffer_state = hphone_try_to_read(connection_socket_fd, download_body_buffer,
               &download_body_buffer_p, download_body_buffer_size);
        if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
          connection_buffers_reset();
        };
        if (buffer_state == BUFFER_STATE_READY) {
          if (download_message_type == HIDDENPHONE_MSG_SOUND) {
            //body_to_file(stdout ,download_body_buffer,
             // download_body_buffer_size, download_body_additional_info_size);
            decoded_sound = hphone_decode_buffer(download_body_buffer,
              download_body_buffer_size, download_body_additional_info_size);
            if (decoded_sound) {
              if (mp2save) save_to_mp2file(download_body_buffer, download_body_buffer_size,
                 download_body_additional_info_size);
              play_link_sound(decoded_sound, playback_device_name);
              discard_link_sound(&decoded_sound);
            } else fprintf(stderr, "Не получилось декодировать\n");
          };
          if (decoded_sound) discard_link_sound(decoded_sound);
          free(download_body_buffer);
          download_body_buffer = NULL;
          download_header_buffer_p = 0;
          download_body_buffer_size = 0;
        };
      };
    };

    if ((upload_queue_first != NULL) && FD_ISSET(connection_socket_fd, &write_fd_kit)) {
      //fprintf(stderr, "select запись на звуковом сокете\n");
      send_one_upload_page();
    };

    if (FD_ISSET(xlib_pipe[0], &read_fd_kit)) {
      read(xlib_pipe[0], &c, 1);
      if (c == 'B') {
        fprintf(stderr, "Нажата кнопка\n");
        if (upload_queue_first || !connection_works) {
          if (!connection_works) fprintf(stderr, "Соединения нет, отсечка\n");
          else fprintf(stderr, "Предыдущее сообщение еще не ушло, отсечка\n");
          continue;
        };
        encoded_sound_first_page = hphone_record_normalize_encode
            (xlib_pipe[0], record_device_name, &encoded_sound_size);
        if (!encoded_sound_first_page) {
          fprintf(stderr, "Отправление отменено\n");
          continue;
        };
        fprintf(stderr, "Отправляю\n");
        hphone_send_header(encoded_sound_size, HIDDENPHONE_MSG_SOUND, 0);
        insert_in_upload_queue(encoded_sound_first_page);
      } else fprintf(stderr, "Кнопка отпущена\n");
    };
  };

  return 0;
};

hp_audio.c

// Файл hp_audio.c

#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096

// Эта функция предназначена для экспериментов
void *get_link_audio_from_stdin(int page_size, long int read_size) {
  fprintf(stderr, "Подайте на вход сырой поток 48000 mono s16 l-e\n");
  void *first_page_struct = NULL;
  struct audio_page_struct *prev_page = NULL;
  struct audio_page_struct *struc;
  long int n = 0;
  int n2;
  int ret, bytes_needed;

  // мы делаем линкованый список
  while (n < read_size) {
    struc = malloc(sizeof(struct audio_page_struct));
    raw_audio_bytes_in_use += sizeof(struct audio_page_struct);
    if (!struc) print_error_and_exit("Память не выделяется struc");
    //fprintf(stderr, "Создана структура страницы по адресу %x\n", (unsigned int)struc);

    ((*struc).samples) = malloc(page_size);
    raw_audio_bytes_in_use += page_size;
    if (!(*struc).samples) print_error_and_exit("Память не выделяется .samples");

    if (prev_page) {
      (*prev_page).next_page = struc;
    };
    if (!first_page_struct) first_page_struct = struc;

    // буфер готов, пора его заполнить
    bytes_needed = page_size;
    n2 = 0;
    while (bytes_needed > 0) {
      ret = fread((*struc).samples+n2, 1, bytes_needed, stdin);
      n2 += ret;
      (*struc).samples_size = n2;
      n += ret;
      bytes_needed -= ret;
      if (ret == 0) break;
    };
    //fprintf(stderr, "Страница содержит %i байт звука.\n", (*struc).samples_size);
    if (feof(stdin)) break;
    if (ferror(stdin)) print_error_and_exit("Ошибка входного потока");

    (*struc).next_page = NULL;
    
    prev_page = struc;
  };

  fprintf(stderr, "Принято %li байт.\n", n);

  return first_page_struct;
};

// эта функция отправляет тело сообщения в файл
void body_to_file (FILE *f, uint8_t *ptr, long int size, long int additional_info_size) {
  long int need_to_send;
  int ret;
  long int buffer_p = additional_info_size; // Перепрыгиваем сразу к данным
  while (buffer_p < size) {
    need_to_send = size - buffer_p;
    if (need_to_send > 2048) need_to_send = 2048;
    ret = fwrite(ptr + buffer_p, 1, need_to_send, f);
    buffer_p += ret;
    if (ret == 0) print_error_and_exit("Какая-то фигня");
  };
};

int record_check_button (int stop_pipe) {
  char c;
  struct timeval time1;
  time1.tv_sec = 0;
  time1.tv_usec = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(stop_pipe, &readfds);
  int ret = select(stop_pipe+1, &readfds, NULL, NULL, &time1);
  if (ret > 0) read(stop_pipe, &c, 1);
  if (c == 'R') return 1;
  return 0;
};

// Внимание, samples_size указывает не на размер страницы, а количество занятых байт в ней
void *create_audio_page (int page_size, struct audio_page_struct **prev_struc) {
  struct audio_page_struct *struc = malloc(sizeof(struct audio_page_struct));
  raw_audio_bytes_in_use += sizeof(struct audio_page_struct);
  if (!struc) print_error_and_exit("Память не выделяется");
  //fprintf(stderr, "Создана структура страницы по адресу %x\n", (unsigned int)struc);
  (struc->samples) = malloc(page_size);
  raw_audio_bytes_in_use += page_size;
  if (!struc->samples) print_error_and_exit("Память не выделяется .samples");
  struc->page_size = page_size;
  struc->samples_size = 0;
  struc->next_page = NULL;
  if (!prev_struc) return struc;
  if (*prev_struc) (*prev_struc)->next_page = struc;
  *prev_struc = struc;

  return struc;
};


void discard_link_sound (void **sound) {
  if (!sound || !*sound) print_error_and_exit("Нуль на входе, освобождать нечего");
  //fprintf(stderr, "Освобождаю raw звук\n");
  struct audio_page_struct *runner = *sound;
  struct audio_page_struct *nextrunner;
  while (runner) {
    raw_audio_bytes_in_use -= runner->page_size;
    free(runner->samples);
    nextrunner = runner->next_page;
    raw_audio_bytes_in_use -= sizeof(struct audio_page_struct);
    free(runner);
    runner = nextrunner;
  };
  *sound = NULL;
  if (raw_audio_bytes_in_use != 0) { 
    fprintf(stderr, "Утечка памяти где-то\n");
    fprintf(stderr, "Занято байт %li\n", raw_audio_bytes_in_use);
  };
};

void save_to_mp2file (uint8_t *buf, long int bufsize, long int addsize) {
  FILE *fileptr;
  char filename[30];
  int filename_int = 0;
  fprintf(stderr, "Сохраняю в файл >");
  
  while (1) {
    snprintf(filename, 28, "message%04i.mp2", filename_int);
    //fprintf(stderr, "%s? \n", filename);
    if (access(filename, F_OK) == -1) break;
    filename_int++;
  }
  fprintf(stderr, "%s\n", filename);
  fileptr = fopen(filename, "wb");
  if (!fileptr) print_error_and_exit("Файл не открывается на запись");
  body_to_file(fileptr, buf, bufsize, addsize);
  fclose(fileptr);
};

void *hphone_record_sound (int stop_pipe, char *record_device_name, int page_size) {
  if (record_check_button(stop_pipe)) return NULL;
  if ((page_size & 1) == 1) print_error_and_exit("Размер страницы не четный");
  fprintf(stderr, "Запись\n");
  int err;
  if (!recorddevice_handle) {
    err = snd_pcm_open(&recorddevice_handle, record_device_name, SND_PCM_STREAM_CAPTURE, 0);
    if (err < 0) {
      fprintf(stderr, "Capture open error: %s\n", snd_strerror(err));
      print_error_and_exit("Звуковая карта не открывается");
    };
    err = snd_pcm_set_params(recorddevice_handle,
                SND_PCM_FORMAT_S16_LE,
                SND_PCM_ACCESS_RW_INTERLEAVED,
                1,
                48000,
                1,
                100000);
    if (err < 0) {
      fprintf(stderr, "Capture open error: %s\n", snd_strerror(err));
      //print_error_and_exit("Параметры не задаются");
    };
  };

  struct audio_page_struct *struc = NULL;
  struct audio_page_struct *prev_struc = NULL;
  struct audio_page_struct *first_struc = NULL;
  snd_pcm_sframes_t frames_read;
  long int audiopage_i, need_frames;
  
  snd_pcm_prepare(recorddevice_handle);
  
  while (1) {
    struc = create_audio_page(page_size, &prev_struc);
    if (!first_struc) first_struc = struc;
    struc->samples_size = page_size;

    // страница создана

    audiopage_i = 0;
    while (audiopage_i < page_size) {
      need_frames = (page_size - audiopage_i) / 2;
      //fprintf(stderr, "Запрашиваю %li фреймов\n", need_frames);
      frames_read = snd_pcm_readi(recorddevice_handle, ((uint8_t *)struc->samples)+audiopage_i, need_frames);
      if (frames_read < 0) frames_read = snd_pcm_recover(recorddevice_handle, frames_read, 0);
      if (frames_read < 0) {
        printf("snd_pcm_readi failed: %s\n", snd_strerror(frames_read));
        print_error_and_exit("Ошибка при записи");
      };
      if (frames_read == 0) print_error_and_exit("snd_pcm_readi не читает");
      audiopage_i += frames_read * 2;
    };
    fprintf(stderr, ".");
    if (record_check_button(stop_pipe)) break;
  };
  fprintf(stderr, "\n");
  snd_pcm_drop(recorddevice_handle);
  if (!dont_close_read_device) {
    snd_pcm_close(recorddevice_handle);
    recorddevice_handle = NULL;
  };
  
  return first_struc;
};


void hphone_normalize_volume (struct audio_page_struct *struc) {
  // этот кусок кода напишите самостоятельно
};


void store_encoded_packet (AVPacket pkt, void **first_encoded_page, void **last_encoded_page) {
  uint8_t *newpage = hphone_make_upload_page(pkt.size);
  void *pagedata = newpage + sizeof(struct upload_page_header);
  memcpy(pagedata, pkt.data, pkt.size);
  struct upload_page_header *prevpageheader;
  if (*last_encoded_page) {
    prevpageheader = *last_encoded_page;
    prevpageheader->nextpage = newpage;
  };
  *last_encoded_page = newpage;
  if (!(*first_encoded_page)) *first_encoded_page = newpage;
};

void *hphone_record_normalize_encode (int stop_pipe, char *record_device_name, long int *encoded_size) {

  void *first_encoded_page = NULL;
  void *last_encoded_page = NULL;
  *encoded_size = 0;

  // Нужно выяснить размер страницы, удобный для кодека
  int ret;
  AVCodec *codec;
  AVCodecContext *c = NULL;
  AVFrame *frame;
  AVPacket pkt;
  int got_output;

  codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
  if (!codec) print_error_and_exit("Кодек MP2 не найден");
  c = avcodec_alloc_context3(codec);
  c->bit_rate = 128000;
  fprintf(stderr, "Кодек MP2, битрейт = %i бит/сек\n", c->bit_rate);
  c->sample_fmt = AV_SAMPLE_FMT_S16;
  c->sample_rate    = 48000;
  c->channel_layout = AV_CH_LAYOUT_MONO;
  c->channels       = 1;
  if (avcodec_open2(c, codec, NULL) < 0) print_error_and_exit("Кодек не открывается");

  int page_size = av_samples_get_buffer_size(NULL, c->channels, c->frame_size,
                                             c->sample_fmt, 0);
  fprintf(stderr, "Размер страницы для кодека %i байт\n", page_size);

  // Теперь запуск записи
  void *link_sound = NULL;
  //link_sound = get_link_audio_from_stdin(page_size, 40000000);

  link_sound = hphone_record_sound(stop_pipe, record_device_name, page_size);

  if (!link_sound) {
    avcodec_close(c);
    av_free(c);
    av_frame_free(&frame);
    return first_encoded_page;
  };

  if (normalize) hphone_normalize_volume(link_sound);

  // Энкодинг
  fprintf(stderr, "Энкодинг\n");

  frame = av_frame_alloc();
  if (!frame) print_error_and_exit("Структура не создается");
  frame->nb_samples     = c->frame_size;
  frame->format         = c->sample_fmt;
  frame->channel_layout = c->channel_layout;
  //fprintf(stderr, "codec frame size = %i\n", c->frame_size);

  struct audio_page_struct *page = link_sound;
  while (page) {
    frame->nb_samples = (*page).samples_size / 2;
    //fprintf(stderr, "Задаю указатели: адрес %x, размер %i, nb %i\n",
    //      (unsigned int)(*page).samples, page_size, frame->nb_samples);
    ret = avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt,
                            (const uint8_t*)(*page).samples, page_size, 0);
    if (ret < 0) print_error_and_exit("Указатели не задаются");

    av_init_packet(&pkt);
    pkt.data = NULL; // packet data will be allocated by the encoder
    pkt.size = 0;

    ret = avcodec_encode_audio2(c, &pkt, frame, &got_output);
    if (ret < 0) print_error_and_exit("Ошибка кодирования");
    if (got_output) {
      store_encoded_packet(pkt, &first_encoded_page, &last_encoded_page);
      *encoded_size += pkt.size;
      av_free_packet(&pkt);
    };

    page = (*page).next_page;
  };

  avcodec_close(c);
  av_free(c);
  av_frame_free(&frame);
  discard_link_sound(&link_sound);

  return first_encoded_page;
};



void play_link_sound (void *link_sound, char *playback_device_name) {
  if (!link_sound) print_error_and_exit("Нулевой указатель на входе");
  int ret;
  struct audio_page_struct *page = link_sound;
  unsigned long int frame_count;
  snd_pcm_t *handle;
  snd_pcm_sframes_t frames;
  if ((ret = snd_pcm_open(&handle, playback_device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
    print_error_and_exit("Выходная аудиокарта не открывается, проверьте имя аудиокарты");
  };
  ret = snd_pcm_set_params(handle,
                           SND_PCM_FORMAT_S16_LE,
                           SND_PCM_ACCESS_RW_INTERLEAVED,
                           1,
                           48000,
                           1,
                           500000);
  if (ret < 0) print_error_and_exit("Выходная аудиокарта, параметры не ставятся");
  fprintf(stderr, "Воспроизвожу звук\n");
  snd_pcm_prepare(handle);
  while (page) {
    //fprintf(stderr, "Читаю страницу по адресу %x\n", (unsigned int)page);
    frame_count = (*page).samples_size / 2; // в каждом фрейме по два байта
    frames = snd_pcm_writei(handle, (*page).samples, frame_count);
    if (frames < 0) frames = snd_pcm_recover(handle, frames, 0);
    if (frames < 0) print_error_and_exit("Аудиокарта, звук не отправляется");
    if (frames > 0 && frames < frame_count) fprintf(stderr, "Звук отправлен на аудиокарту не полностью,\n"
                   "отправлено %li фреймов, а нужно %li фреймов\n", frame_count, frames);
    page = (*page).next_page;
  };
  snd_pcm_drain(handle); // жду, пока звук доиграет
  snd_pcm_close(handle);
  fprintf(stderr, "Готово\n");
};

void hphone_make_beep (int tone, int sample_rate, struct audio_page_struct *audiopage) {
  if (!audiopage || !audiopage->samples || 
    sample_rate <= 0 || audiopage->page_size <= 0) print_error_and_exit("Ошибка make beep");
  memset(audiopage->samples, 0x00, audiopage->page_size);
  int period = sample_rate / tone;
  int half_period = period / 2;
  int i = 0;
  int buffer_p = 0;
  int16_t *writehead = audiopage->samples;
  while (buffer_p < (audiopage->page_size)) {
    *writehead = (i > half_period) ? -60000 : 60000;
    i++;
    if (i >= period) i = 0;
    buffer_p += 2;
    writehead++;
  };
  audiopage->samples_size = audiopage->page_size;
};

void *hphone_decode_buffer (uint8_t *buffer, long int size, long int additional_size) {
  long int buffer_p = 0;
  struct audio_page_struct *decoded_page = NULL;
  struct audio_page_struct *decoded_first_page = NULL;
  struct audio_page_struct *decoded_prev_page = NULL;
  AVCodec *codec;
  AVCodecContext *c= NULL;
  int len;
  //uint8_t inbuf[AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
  AVPacket avpkt;
  AVFrame *decoded_frame = NULL;

  av_init_packet(&avpkt);

  printf("Декодирую\n");

  codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
  if (!codec) print_error_and_exit("Кодек MP2 не найден");
  c = avcodec_alloc_context3(codec);
  if (avcodec_open2(c, codec, NULL) < 0) print_error_and_exit("Кодек не открывается");
  
  buffer_p = additional_size;
  avpkt.data = buffer+buffer_p;
  avpkt.size = (size - buffer_p > 4096) ? 4096 : size - buffer_p;
  while (avpkt.size > 0) {
    int got_frame = 0;
    if (!decoded_frame) {
      if (!(decoded_frame = av_frame_alloc())) print_error_and_exit("Память не выделяется");
    };
    len = avcodec_decode_audio4(c, decoded_frame, &got_frame, &avpkt);
    if (len < 0) {
      fprintf(stderr, "Ошибка декодирования\n");
      break;
    };
    if (got_frame) {
      if (!decoded_first_page) {
        // Сигнал о начале сообщения
        decoded_first_page = create_audio_page((48000 / 10)*2, &decoded_prev_page);
        hphone_make_beep(2000, 48000, decoded_first_page);
      };
      int data_size = av_samples_get_buffer_size(NULL, c->channels,
                           decoded_frame->nb_samples,
                           c->sample_fmt, 1);
      decoded_page = create_audio_page(data_size, &decoded_prev_page);
      if (!decoded_first_page) decoded_first_page = decoded_page;
      memcpy(decoded_page->samples, decoded_frame->data[0], data_size);
      decoded_page->samples_size = data_size;
    };
    avpkt.size -= len;
    avpkt.data += len;
    buffer_p += len;
    if (avpkt.size < AUDIO_REFILL_THRESH) {
      // Наполнить буфер
      avpkt.size = (size - buffer_p > 4096) ? 4096 : size - buffer_p;
    };
  };

  if (decoded_first_page) {
    // Сигнал о конце сообщения
    decoded_page = create_audio_page((48000 / 10)*2, &decoded_prev_page);
    hphone_make_beep(1000, 48000, decoded_page);
  };
  
  return decoded_first_page;
};

hp_key.c

// Файл hp_key.c

#define HPHONE_KEY XK_F12

void hphone_monitor_key(int pipe_fd) {
  Display *d;
  XEvent event;
  int autorepeat_down_pending, i;

  struct timespec little_timeout = { 0, 10000000 };

  fprintf(stderr, "Для включения записи нажмите %s\n", XKeysymToString(HPHONE_KEY));
  d = XOpenDisplay(NULL);
  if ( d != NULL ) {
    XGrabKey(d, XKeysymToKeycode(d, HPHONE_KEY), AnyModifier,
       DefaultRootWindow(d), True, GrabModeAsync, GrabModeAsync);
    autorepeat_down_pending = 0;
    while (1) {
      XNextEvent(d, &event);
      if ( event.type == KeyPress ) {
        KeySym s = XLookupKeysym(&event.xkey, 0);
        if ( s == HPHONE_KEY ) {
          if (autorepeat_down_pending) autorepeat_down_pending = 0;
          else {
            //fprintf(stderr, "Клавиша нажата\n");
            write(pipe_fd, "B", 1);
          };
        };
      };
      if ( event.type == KeyRelease ) {
        KeySym s = XLookupKeysym(&event.xkey, 0);
        if ( s == HPHONE_KEY ) {
          // этот код обнаруживает автоповторы и вычищает их
          for (i = 0; i < 3; i++) {
            nanosleep(&little_timeout, NULL);
            if (XEventsQueued(d, QueuedAfterReading)) {
              autorepeat_down_pending = 1;
              break;
            };
          };
          if (!autorepeat_down_pending) {
            //fprintf(stderr, "Клавиша отпущена\n");
            write(pipe_fd, "R", 1);
          };
        };
      };
    };
  };
};

Как это запустить?

Программу можно запустить только на Linux.

Программа для запуска требует, чтобы в системе стояли некоторые библиотеки, их нужно установить. Для этого выполните следующие две команды от имени супер-пользователя:

apt-get update
apt-get install libavcodec-dev libavutil-dev libasound2-dev libx11-dev

Эта команда скачает из интернета все что нужно и установит библиотеки.

Теперь создайте папку с тремя файлами исходного кода, наполните их кодом.

Компилируем

gcc -Wall hp.c -lasound -lavcodec -lavutil -lX11 -o hp

Компиляция прошла без ошибок? Хорошо. В результате у вас появился исполняемый файл hp который мы сейчас запустим:

./hp

Чтобы остановить программу, достаточно нажать Ctrl+C

Примеры использования

Программа может быть сервером или клиентом. Для начала определитесь, где будет сервер, а где клиент. Сервер может обслуживать клиентов только по одному.

Чтобы запустить программу в режиме сервера:

./hp -bind 42232 -pass xyzzy

На локалхосте открывается порт для входящих соединений, порт виден только для программ, работающих на этом-же компьютере. Другая команда открывает порт на всех интерфейсах, порт будет виден с соседних компьютеров:

./hp -bindclearnet 42232

Слово clearnet означает, что трафик попрет не через Tor, а напрямую без шифрования по белому интернету, чтобы открыть сервер в Tor, следует открывать порт только на локалхосте.

На том же компьютере, где запущен сервер, нужно запустить Tor и настроить HiddenService. Процесс Тора будет ловить соединения из луковой сети и перенаправлять их на наш порт.

При запуске программа подключается к Иксам (X server, это такая программа, через которую другие программы рисуют на экране окошки, буквы, а взамен получают сообщения о мышекликах и нажатиях клавиш) и вешает ловушку на клавишу F12. Если вы хотите запустить две программы на одном компьютере для проверки работы, то вам нужно выключить ловушку у одной из программ, для этого есть опция -nobutton

./hp -clearnet 127.0.0.1 42232 -pass xyzzy -nobutton

Эта команда пробует подключиться напрямую к порту 42232 на локалхосте, и если соединение будет принято, то программа отправляет пароль. Сервер либо закрывает соединение, либо говорит, что пароль правильный. После этого можно нажать и удерживать F12 для записи. После отпускания клавиши сообщение сжимается, отправляется, на другом конце распаковывается и воспроизводится.

Следующая команда вместо прямого соединения пытается найти Тор сначала на порту 9150, потом на 9050.

./hp -tor https://abcdabcdabcd1234.onion.tor.my 42232

Если получилось, то программа отправляет SOCKS заголовок с .onion адресом и ждет, пока Тор установит соединение, затем все происходит как обычно, но только весь траффик идет через Тор.

Чуть подробнее о том, как настроить HiddenService

Для клиента достаточно, чтобы на том же компьютере работал TorBrowser. В Торбраузер встроен Тор, который слушает на порту 9150.

Если у вас в линуксе стоит консольный Тор, то он будет слушать на порту 9050.

А теперь чуть по ближе к HiddenService, чтобы его настроить, нужно найти конфигурационный файл Тора.

У Торбраузера он лежит по адресу tor-browser-folder/Browser/TorBrowser/Data/Tor/torrc

Если у вас консольный Тор, то скорее всего это /etc/tor/torrc

Это обыкновенный текстовый файл. Если это консольный Тор, то найдите в файле и уберите в начале символы решетки у этих двух строк:

#HiddenServiceDir /var/lib/tor/hidden_service/
#HiddenServicePort 80 127.0.0.1:80

HiddenServiceDir - это папка, в которой Тор будет искать ключ от скрытого сервиса. Если его нет, то он будет сгенерирован и положен в эту папку.

HiddenServicePort указывает, на какой порт будут направляться запросы из Луковой сети. Его нужно поменять примерно так (номер порта поставьте любой с потолка, но не более 65535):

HiddenServicePort 42232 127.0.0.1:42232

Если вы настраиваете Torbrowser, то вам нужно просто добавить эти пару строк в файл. Папку для ключа лучше указать там, куда у Тора будет доступ, например /home/bb/hiddenphone/

Чтобы попросить процесс Тора проглотить новый конфигурационный файл, выполните

killall -s SIGHUP tor

Если Тор запустился как надо, то в папке появится приватный ключ, и рядом с ним файлик hostname, который содержит адрес только что созданного .onion скрытого сервиса.

Приватный ключ никогда никому не давайте. А вот адрес onion мож%

Протокол сообщений

Поток данных между программами состоит из сообщений. Каждое сообщение начинается с заголовка. Первые четыре байта, это сигнатура HPHO, затем идет длина сообщения в формате big-endian с учетом доп. данных но без учета заголовка, затем идет номер, указывающий на тип сообщения, затем число, указывающее количество дополнительных данных, которые находятся в начале тела сообщения. Следом за заголовком идет тело сообщения, длина которого указана в заголовке. Следом может идти заголовок следующего сообщения. Соединение можно закрыть в любой момент.

Дополнительные данные в этой версии не используются, но они есть для совместимости с будущими версиями программы.

Пока не реализованные функции

Normalize. В коде содержится функция hphone_normalize_volume() которая должна измерить максимальный уровень сообщения, затем поднять громкость всего сообщения до предела, но чтобы не было искажений. Если у вас есть желание, то напишите эту функцию самостоятельно.

Модель безопасности

Несмотря на то, что все данные зашифрованы, остается возможность профилирования размера передаваемых данных и направления их следования. Если внешний наблюдатель будет фиксировать размер передаваемых кусков и точное время их появления, то он заметит отличия от протокола HTTP. Протокол HiddenPhone, в отличие от HTTP, не отправляет ответ сразу. В том числе он не отправляет сообщение о доставке звука. Внешний наблюдатель может сделать вывод, что вы используете не HTTP сессию а что-то другое.

Рекомендуется не давать onion адрес телефона посторонним людям, чтобы уберечь его от перебора пароля.

Эта программа работает со звуком, следовательно, стоит подумать о звуковой деанонимизации. Программу следует использовать для связи с людьми, с которыми вы знакомы лично, и общение с которыми вы хотите зашифровать.

Как и в любой программе для защиты информации, уровень безопасности никогда не бывает абсолютным. С удовольствием отвечу на все вопросы по поводу безопасности данной программы.

Вот такая программка. С удовольствием прочитаю любые комментарии и отчеты о запуске.

Всегда ваш(а), ББ.

 Вложения

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: spurdo on 2018-05-01 14 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

strcmp((char *)intro_buffer+16, password) == 0 ) {
          fprintf(stderr, "Пароль правильный\n");

Лучше использовать сравнение с постоянным временем исполнения (constant-time comparison).

ББ пишет:

Поток данных между программами состоит из сообщений

Вы бы описали формат более строго. Рекомендую также обратить внимание на google protobuf.


special-purpose undeground research and development organization
Зарегистрирован только на Рунионе. Связь только через ЛС форума.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ББ on 2018-05-01 16 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

spurdo, понятно, сейчас поправлю.

Проблема

spurdo указал на слабое место при проверке пароля. При проверке программа читает ровно 2048 байт из сокета, а затем закрывает соединение, если пароль неправильный. Время проверки пароля меняется в зависимости от ввода. Функция strcmp() сравнивает две строки по одной букве и прекращает сравнение при обнаружении различия. Чем больше правильных букв в начале пароля, тем больше символов проверяет функция и тем дольше она работает. Это сильно упрощает и убыстряет подбор пароля.

Патч

Подробное описание изменений

Чтобы исправить проблему, нужно внести некоторую дополнительную функциональность в программу.

Откройте файл hp.c и найдите в нем строку

    strcmp((char *)intro_buffer+16, password) == 0 ) {

Замените на

    constanttime_cmp(intro_buffer+16, 2048-16-25) ) {

Далее найдите кусок (после парсинга опций)

  // ставим ловушку на клавишу в Xlib

Перед этим куском вставьте следующую строку

  constanttime_cmp_prepare(password);

Ну и наконец найдите

#include "hp_key.c"
#include "hp_audio.c"

Перед ними или после них вставьте этот кусок

char cmp_buffer[2048];
int cmp_buffer_ready = 0;
void constanttime_cmp_prepare(char *text) {
  memset(cmp_buffer, 0x00, sizeof(cmp_buffer));
  strncpy(cmp_buffer, text, sizeof(cmp_buffer));
  cmp_buffer_ready = 1;
};
int constanttime_cmp(unsigned char *user_input, int depth) {
  // мой крутой алгоритм проверки пароля, время выполнения не зависит от ввода
  int i;
  int right = 0;
  int wrong = 0;
  if (!cmp_buffer_ready) print_error_and_exit("Ошибка инициализации");
  for (i=0; i<depth; i++) {
    if (user_input[i] == cmp_buffer[i]) right++;
    else wrong++;
  };
  if (wrong > 0) return 0;
  return 1;
};

Нашелся еще один маленький баг:

Найдите кусок

    if (upload_queue_first == NULL) upload_queue_last = NULL;
    if (upload_queue_first == NULL) fprintf(stderr, "Сообщение ушло, очередь отправки пуста\n");
    if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");

Замените на

    if (upload_queue_first == NULL) {
      upload_queue_last = NULL;
      fprintf(stderr, "Сообщение ушло, очередь отправки пуста\n");
      if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");
    };

Скомпилируйте заново. Должно работать.

Файл hp.c с примененными изменениями

// Файл hp.c
// Программа HiddenPhone впервые публикуется на форуме Runion https://lwplxqzvmgu43uff.onion.tor.my
// Рядом с файлом hp.c должны лежать файлы hp_audio.c и hp_key.c
// Компилировать командой:
// gcc -Wall hphone1.c -lasound -lavcodec -lavutil -lX11 -o hphone1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>

#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "libavcodec/avcodec.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/samplefmt.h"

#include "../include/alsa/asoundlib.h"

#define TOR_PORT_FIRST 9150
#define TOR_PORT_SECOND 9050

uint16_t onion_target_port = 0;
uint16_t bind_port = 0;
uint16_t clearnet_port = 0;
uint16_t tor_port = TOR_PORT_FIRST; // Программа автоматически пробует оба порта
  // Предпочтение отдается порту Tor-Browser
char *oniondomain = "";
char *clearnet_ip = "";

int server_socket_fd = 0;
int connection_socket_fd = 0;
int intro_socket_fd = 0;
int max_fd;
int connection_works = 0;
int intro_works = 0;

int noheartbeat_count = 0;

int xlib_pid = -1;

enum {BUFFER_STATE_READY, BUFFER_STATE_PARTIAL, BUFFER_STATE_ERROR_FD_CLOSED};

// очередь на отправку
struct upload_page_header {void *nextpage; long int datasize;};

long int upload_queue_bytes_in_use = 0;
uint8_t *upload_queue_first = NULL;
uint8_t *upload_queue_last = NULL;
long int upload_queue_p = 0;

uint8_t intro_buffer [2048];
long int intro_buffer_p = 0;

uint8_t download_header_buffer [16];
long int download_header_buffer_p = 0;

int disablebutton = 0;
int enableheartbeat = 0;
int temporary_enableheartbeat = 0;

int normalize = 0;
int mp2save = 0;

int dont_close_read_device = 0;
snd_pcm_t *recorddevice_handle = NULL;

struct parsed_header_struct {int signature_ok;
                             uint32_t bodysize;
                             uint32_t msg_type;
                             uint32_t additional_size;} parsed_header;

uint8_t *download_body_buffer = NULL;
long int download_body_buffer_size = 0;
long int download_body_additional_info_size = 0;
uint32_t download_message_type;
long int download_body_buffer_p = 0;

unsigned long int raw_audio_bytes_in_use = 0;

enum {CONNECT, BIND, NOPE} mode = NOPE;
enum {TOR, CLEARNET} clearnet = TOR;

// типы сообщений
#define HIDDENPHONE_MSG_AUTH_PASSWORD 2
#define HIDDENPHONE_MSG_PASSWORD_OK 10
#define HIDDENPHONE_MSG_SOUND 30
#define HIDDENPHONE_MSG_SOUND_ARRIVED 40 // не используется
#define HIDDENPHONE_MSG_HEARTBEAT_PLEASE 75
#define HIDDENPHONE_MSG_HEARTBEAT 76
#define HIDDENPHONE_MSG_HEARTBEAT_OK 77
#define HIDDENPHONE_MSG_HEARTBEAT_ENABLE 78

char *password = "";

int disable_socksproxy = 0;

struct audio_page_struct {void *next_page; int page_size; void *samples; int samples_size;};

void print_error_and_exit(char * str) {
  fprintf(stderr, "Error: %s\n", str);
  if (xlib_pid != -1) kill(xlib_pid, SIGTERM);
  exit (1);
};

int hphone_try_to_read (int fd, uint8_t *buffer, long int *buffer_p, long int buffer_size) {
  long int read_count_needed = buffer_size - (*buffer_p);
  int ret = read(fd, buffer + (*buffer_p), read_count_needed);
  if (ret == -1) {
    perror("read");
    fprintf(stderr, "Ошибка чтения, закрываю дескриптор\n");
    close(fd);
    return BUFFER_STATE_ERROR_FD_CLOSED;
  };
  if (ret == 0) {
    fprintf(stderr, "Соединение закрылось\n");
    close(fd);
    return BUFFER_STATE_ERROR_FD_CLOSED;
  };
  if (ret > 0) {
    (*buffer_p) += ret;
    if ((*buffer_p) == buffer_size) return BUFFER_STATE_READY;
  };
  return BUFFER_STATE_PARTIAL;
};


/* Функция ставит страницу в очередь на отправку, если к странице
   привязаны еще страницы, то они тоже ставятся */

void insert_in_upload_queue (void *page) {
  struct upload_page_header *lastpage_header;
  struct upload_page_header *new_last_page_header = page;
  if (!upload_queue_first) upload_queue_first = page;
  if (upload_queue_last) {
    lastpage_header = (struct upload_page_header*) upload_queue_last;
    lastpage_header->nextpage = page;
  };

  // Нужно найти последнюю страницу и записать ее адрес в upload_queue_last
  while (new_last_page_header->nextpage) new_last_page_header = new_last_page_header->nextpage;
  upload_queue_last = (uint8_t *) new_last_page_header;
};

void connection_buffers_reset () {
  connection_works = 0;
  download_header_buffer_p = 0;
  if (download_body_buffer) free(download_body_buffer);
  download_body_buffer = NULL;
  download_body_buffer_size = 0;
  download_body_buffer_p = 0;

  // этот код отчищает очередь отправки
  void *nextpage;
  struct upload_page_header *header;
  while (upload_queue_first) {
    header = (struct upload_page_header *) upload_queue_first;
    upload_queue_bytes_in_use -= sizeof(struct upload_page_header) + header->datasize;
    nextpage = header->nextpage;
    free(upload_queue_first);
    upload_queue_first = nextpage;
  };
  upload_queue_last = NULL;
  upload_queue_p = 0;
  if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");
};

void send_one_upload_page (void) {
  if (!upload_queue_first) print_error_and_exit("Нет страниц для отправки");
  struct upload_page_header *header = (struct upload_page_header *) upload_queue_first;
  uint8_t *pagedata = upload_queue_first + sizeof(struct upload_page_header);
  long int need_to_send = header->datasize - upload_queue_p;
  if (need_to_send == 0) print_error_and_exit("Эту страницу мы уже отправили");
  int send_flags = MSG_NOSIGNAL | MSG_DONTWAIT | ((header->nextpage) ? MSG_MORE : 0);
  int ret = send(connection_socket_fd, pagedata + upload_queue_p, need_to_send, send_flags);
  if (ret > 0) upload_queue_p += ret;
  if (ret == 0) print_error_and_exit("send вернул 0");
  if (ret == -1) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      fprintf(stderr, "Буфер отправления переполнился\n");
    } else {
      perror("send");
      fprintf(stderr, "Ошибка соединения, через некоторое время откроем снова\n");
      close(connection_socket_fd);
      connection_buffers_reset();
    };
  };
  if (ret == need_to_send) {
    //fprintf(stderr, "Отправлена страница %x\n", (unsigned int)upload_queue_first);
    // страница отправлена, теперь нужно освободить память
    void *prev_page = upload_queue_first;
    upload_queue_bytes_in_use -= sizeof(struct upload_page_header) + header->datasize;
    upload_queue_first = header->nextpage;
    //fprintf(stderr, "Следующая страница %x\n", (unsigned int)upload_queue_first);
    free(prev_page);
    prev_page = NULL;
    upload_queue_p = 0;
    if (upload_queue_first == NULL) {
      upload_queue_last = NULL;
      fprintf(stderr, "Сообщение ушло, очередь отправки пуста\n");
      if (upload_queue_bytes_in_use != 0) fprintf(stderr, "Утечка памяти\n");
    };
  };
};

uint32_t hphone_get_value (uint8_t *pointer) {
  uint32_t lo = ntohl(*((uint32_t *)pointer));
  return lo;
};

// эта функция заполняет поля структуры parsed_header
void hphone_parse_header(uint8_t *pointer) {
  parsed_header.signature_ok = (strncmp((char *)pointer, "HPHO", 4) == 0) ? 1 : 0;
  parsed_header.bodysize = hphone_get_value(pointer+4);
  parsed_header.msg_type = hphone_get_value(pointer+8);
  parsed_header.additional_size = hphone_get_value(pointer+12);
};

void list_options () {
  print_error_and_exit("Неправильно задана опция, вот список опций:\n\n"
"-bind PORT\n-bindclearnet PORT    - открыть порт на localhost или на всех интерфейсах\n"
"-tor ONIONADDRESS PORT - подключиться к onion адресу через Тор\n"
"-clearnet IPADDERSS PORT  - подключиться напрямую к порту\n"
"-pass PASSWORD        - пароль для проверки\n"
"-pd PLAYBACKDEVICE\n"
"-rd RECORDDEVICE      - выбор аудиокарты, напр. plughw:1\n"
"-dontclose            - не закрывать карту после записи. Помогает, если карта медленно включается\n"
"-mp2save              - создавать mp2 файлы с входящими сообщениями\n"
"-heartbeat            - посылать heartbeat сообщения для проверки соединения\n"
"-normalize            - пока не реализованная функция,\n"
" автоматически поднимает громкость сообщения перед отправкой\n"
"-nobutton             - выключить кнопку\n");

};

void * hphone_make_upload_page (long int size) {
  void *page = malloc(sizeof(struct upload_page_header) + size);
  upload_queue_bytes_in_use += sizeof(struct upload_page_header) + size;
  if (!page) print_error_and_exit("Память не выделяется");
  struct upload_page_header *header = page;
  header->datasize = size;
  header->nextpage = NULL;
  return page;
};

void hphone_send_header (uint32_t body_size, uint32_t msg_type, uint32_t info_size) {
  uint8_t *page = hphone_make_upload_page(16);
  uint8_t *pagedata = page+sizeof(struct upload_page_header);
  strcpy((char *)pagedata, "HPHO");
  *((uint32_t *)(pagedata+4)) = htonl(body_size);
  *((uint32_t *)(pagedata+8)) = htonl(msg_type);
  *((uint32_t *)(pagedata+12)) = htonl(info_size);

  // Кусок памяти заполнен, теперь его надо поставить в очередь на отправку
  insert_in_upload_queue(page);
};

void reconnect_socket (void) {
  if (connection_works) {
    fprintf(stderr, "Старое соединение закрывается\n");
    close(connection_socket_fd);
    connection_buffers_reset();
  };
  if (mode != CONNECT) print_error_and_exit("Ошибка режима");
  connection_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in connect_sockaddr_in;
  connect_sockaddr_in.sin_family = AF_INET;
  int ret;
  if (clearnet == TOR) {
    connect_sockaddr_in.sin_port = htons(tor_port);
    ret = inet_aton("127.0.0.1", &connect_sockaddr_in.sin_addr);
  };
  if (clearnet == CLEARNET) {
    fprintf(stderr, "Осторожно CLEARNET, шифрование выключено\n");
    connect_sockaddr_in.sin_port = htons(clearnet_port);
    ret = inet_aton(clearnet_ip, &connect_sockaddr_in.sin_addr);
  };
  if (!ret) print_error_and_exit("IP не валидный");
  fprintf(stderr, "Подсоединяюсь к порту %i, по адресу %s\n", ntohs(connect_sockaddr_in.sin_port),
       inet_ntoa(connect_sockaddr_in.sin_addr));
  ret = connect(connection_socket_fd, (struct sockaddr *) &connect_sockaddr_in, sizeof (connect_sockaddr_in));
  if (ret == -1) {
    if (errno == ECONNREFUSED) {
       fprintf(stderr, "Соединение сброшено, будет повторная попытка\n");
       close(connection_socket_fd);

       if (clearnet == TOR && tor_port == TOR_PORT_FIRST) {
         tor_port = TOR_PORT_SECOND; // если на 9150 не получилось, сразу же пробуем на 9050
         reconnect_socket();
         return;
       };
    } else if (errno == EINPROGRESS) {
      fprintf(stderr, "Асинхронное открытие.\n");
      connection_works = 1;
    } else {
      perror("connect");
      fprintf(stderr, "Ошибка при открытии соединения\n");
    }
  } else {
    connection_works = 1;
    fprintf(stderr, "Соединение принято\n");
  }

  uint16_t socks4a_port;
  char socks4a_header[] = "\x04\x01\x00\x50\x00\x00\x00\x01user";
  uint8_t socks_response[9];
  uint8_t *response_byte;
  int oniondomain_length = strlen(oniondomain)+1;
  if (connection_works) {
    if (clearnet == TOR) {
      fprintf(stderr, "Посылаю заголовок SOCKSv4a\n");
      socks4a_port = htons(onion_target_port);
      memcpy(socks4a_header+2, &socks4a_port, 2);
      ret = send(connection_socket_fd, socks4a_header, sizeof(socks4a_header), MSG_MORE);
      if (ret < sizeof(socks4a_header)) print_error_and_exit("Не отправляется");
      ret = send(connection_socket_fd, oniondomain, oniondomain_length, 0);
      if (ret < oniondomain_length) print_error_and_exit("Слишком длинный адрес");
      memset(socks_response, 0, 9);
      ret = recv(connection_socket_fd, socks_response, 8, MSG_WAITALL);
      if (ret < 8) print_error_and_exit("Ответ не читается");
      response_byte = socks_response+1;
      switch(*response_byte) {
        case 0x5A: fprintf(stderr, "Запрос принят\n");
        break;
        case 0x5B: print_error_and_exit("SOCKS запрос отклонен");
        break;
        default: print_error_and_exit("SOCKS непонятный ответ");
      };
    };
    fprintf(stderr, "Посылаю 2048 байт с паролем\n");
    hphone_send_header(2048 - 16, HIDDENPHONE_MSG_AUTH_PASSWORD, 0);
    void *intro_page = hphone_make_upload_page (2048 - 16);
    void *intro_page_data = intro_page + sizeof(struct upload_page_header);
    memset(intro_page_data, '\0', 2048 - 16);
    strncpy((char *)intro_page_data, password, 2048-50);
    insert_in_upload_queue(intro_page);
    if (enableheartbeat) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_ENABLE,0);
  };
};


char cmp_buffer[2048];
int cmp_buffer_ready = 0;
void constanttime_cmp_prepare(char *text) {
  memset(cmp_buffer, 0x00, sizeof(cmp_buffer));
  strncpy(cmp_buffer, text, sizeof(cmp_buffer));
  cmp_buffer_ready = 1;
};
int constanttime_cmp(unsigned char *user_input, int depth) {
  // мой крутой алгоритм проверки пароля, время выполнения не зависит от ввода
  int i;
  int right = 0;
  int wrong = 0;
  if (!cmp_buffer_ready) print_error_and_exit("Ошибка инициализации");
  for (i=0; i<depth; i++) {
    if (user_input[i] == cmp_buffer[i]) right++;
    else wrong++;
  };
  if (wrong > 0) return 0;
  return 1;
};

#include "hp_key.c"
#include "hp_audio.c"

int main (int argc, char **argv) {
  int ret, i;
  int buffer_state;

  void *decoded_sound = NULL;

  char *record_device_name = "default";
  char *playback_device_name = "default";

  void *encoded_sound_first_page = NULL;
  long int encoded_sound_size = 0;

  avcodec_register_all();

  // парсим опции
  for (i = 1; i < argc; i++) {
    if (strncmp(argv[i], "-bind", 5) == 0) {
      if (!argv[i+1]) list_options();
      if (atol(argv[i+1]) > 65535) print_error_and_exit("Номер порта слишком большой");
      bind_port = atol(argv[i+1]);
      if (bind_port <= 0) print_error_and_exit("Недопустимый номер");
      mode = BIND;
      if (strcmp(argv[i], "-bindclearnet") == 0) clearnet = CLEARNET;
      i++;
      continue;
    };
    if (strcmp(argv[i], "-pass") == 0) {
      if (!argv[i+1]) list_options();
      password = argv[i+1];
      i++;
      continue;
    };
    if (strcmp(argv[i], "-clearnet") == 0) {
      if (!argv[i+1]) list_options();
      if (!argv[i+2]) list_options();
      clearnet_ip = argv[i+1];
      if (atol(argv[i+2]) > 65535) print_error_and_exit("Номер порта слишком большой");
      clearnet_port = atol(argv[i+2]);
      if (clearnet_port <= 0) print_error_and_exit("Недопустимый номер");
      mode = CONNECT;
      clearnet = CLEARNET;
      i+=2;
      continue;
    };
    if (strcmp(argv[i], "-tor") == 0) {
      if (!argv[i+1]) list_options();
      if (!argv[i+2]) list_options();
      oniondomain = argv[i+1];
      if (atol(argv[i+2]) > 65535) print_error_and_exit("Номер порта слишком большой");
      onion_target_port = atol(argv[i+2]);
      mode = CONNECT;
      clearnet = TOR;
      i+=2;
      continue;
    };
    if (strcmp(argv[i], "-rd") == 0) {
      if (!argv[i+1]) list_options();
      record_device_name = argv[i+1];
      i++;
      continue;
    };
    if (strcmp(argv[i], "-pd") == 0) {
      if (!argv[i+1]) list_options();
      playback_device_name = argv[i+1];
      i++;
      continue;
    };

    if (strcmp(argv[i], "-dontclose") == 0) {
      dont_close_read_device = 1;
      continue;
    };
    if (strcmp(argv[i], "-nobutton") == 0) {
      disablebutton = 1;
      continue;
    };
    if (strcmp(argv[i], "-heartbeat") == 0) {
      enableheartbeat = 1;
      continue;
    };
    if (strcmp(argv[i], "-normalize") == 0) {
      normalize = 1;
      continue;
    };
    if (strcmp(argv[i], "-mp2save") == 0) {
      mp2save = 1;
      continue;
    };

    fprintf(stderr, "Неправильная опция %s\n", argv[i]);
    list_options();
  };
  
  constanttime_cmp_prepare(password);

  // ставим ловушку на клавишу в Xlib
  int xlib_pipe[2];
  if (pipe(xlib_pipe) != 0) print_error_and_exit("Пайп не открывается");
  if (!disablebutton) {
    xlib_pid = fork();
    if (xlib_pid == 0) {
      hphone_monitor_key(xlib_pipe[1]);
      exit(0); // Заканчиваю дочерний процесс
    } else fprintf(stderr, "Дочерний процесс %i мониторит кнопку\n", xlib_pid);
  };


  struct sockaddr_in local_sockaddr_in;
  if (mode == BIND) {
    local_sockaddr_in.sin_family = AF_INET;
    local_sockaddr_in.sin_port = htons(bind_port);
    if (clearnet == TOR) {
      ret = inet_aton("127.0.0.1", &local_sockaddr_in.sin_addr);
      if (!ret) print_error_and_exit("IP не валидный");
    } else if (clearnet == CLEARNET) {
      fprintf(stderr, "Осторожно CLEARNET, порт будет виден с соседних компьютеров\n");
      local_sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
    };
    server_socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    fprintf(stderr, "Открываю порт %i, на адресе %s\n", ntohs(local_sockaddr_in.sin_port),
         inet_ntoa(local_sockaddr_in.sin_addr));
    ret = bind(server_socket_fd, (struct sockaddr *) &local_sockaddr_in, sizeof(local_sockaddr_in));
    if (ret) {
      perror("bind");
      print_error_and_exit("Порт не привязывается");
    };
    ret = listen(server_socket_fd, 5);
    if (ret) {
      perror("listen");
      print_error_and_exit("Порт не открывается на прием");
    };
  };

  if (mode == CONNECT) {
    reconnect_socket();
  };

  if (mode == NOPE) fprintf(stderr, "Вы не задали режим работы, соединения не будет.\n"
   "Используйте опции -tor или -bind\n");

  fd_set read_fd_kit, write_fd_kit;
  struct timeval time_value;
  char c = ' ';

  while (1) {
    // главный цикл
    FD_ZERO(&read_fd_kit);
    FD_ZERO(&write_fd_kit);
    FD_SET(xlib_pipe[0], &read_fd_kit);
    if (connection_works) FD_SET(connection_socket_fd, &read_fd_kit);
    if (mode == BIND) FD_SET(server_socket_fd, &read_fd_kit);
    if (intro_works) FD_SET(intro_socket_fd, &read_fd_kit);
    if (upload_queue_first && connection_works) FD_SET(connection_socket_fd, &write_fd_kit);

    max_fd = xlib_pipe[0];
    if (connection_socket_fd > max_fd) max_fd = connection_socket_fd;
    if (server_socket_fd > max_fd) max_fd = server_socket_fd;
    if (intro_socket_fd > max_fd) max_fd = intro_socket_fd;

    time_value.tv_sec = (mode == BIND) ? 40 : 60;
    time_value.tv_usec = 0;

    //fprintf(stderr, "Захожу в select\n");
    ret = select(max_fd+1, &read_fd_kit, &write_fd_kit, NULL, &time_value);
    if (ret == -1) {
      perror("select()");
      print_error_and_exit("Ошибка с дескрипторами");
    };

    if (ret == 0 && temporary_enableheartbeat) {
      if (mode == BIND && connection_works) {
        fprintf(stderr, "Посылаю heartbeat\n");
        hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT, 0);
      };
      if (mode == CONNECT) {
        noheartbeat_count++;
        if (noheartbeat_count == 3 && connection_works) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_PLEASE, 0);
        if (noheartbeat_count > 5) {
          noheartbeat_count = 0;
          reconnect_socket();
        };
      };
    };

    if (mode == CONNECT && !connection_works) {
      if (tor_port == TOR_PORT_SECOND) tor_port = TOR_PORT_FIRST;
      reconnect_socket();
    }

    if (intro_works && FD_ISSET(intro_socket_fd, &read_fd_kit)) {
      fprintf(stderr, "На пригласительный сокет пришла порция данных\n");
      buffer_state = hphone_try_to_read(intro_socket_fd, intro_buffer, &intro_buffer_p, sizeof(intro_buffer));
      if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
        intro_works = 0;
        intro_buffer_p = 0;
      };
      
      if (buffer_state == BUFFER_STATE_READY) {
        // проверка пароля
        intro_buffer[2047] = '\0';
        hphone_parse_header(intro_buffer);
        
        if (parsed_header.signature_ok &&
            parsed_header.bodysize == 2048 - 16 &&
            parsed_header.msg_type == HIDDENPHONE_MSG_AUTH_PASSWORD &&
            parsed_header.additional_size == 0 &&
          constanttime_cmp(intro_buffer+16, 2048-16-25) ) {
          fprintf(stderr, "Пароль правильный\n");
          if (connection_works) {
            fprintf(stderr, "Закрываю старое звуковое соединение, начинаю использовать новое\n");
            connection_buffers_reset();
            ret = close(connection_socket_fd);
            if (ret) fprintf(stderr, "Предыдущий звуковой сокет при закрытии сообщил о какой-то фигне\n");
          };
          connection_socket_fd = intro_socket_fd;
          intro_works = 0;
          connection_works = 1;
          hphone_send_header(0, HIDDENPHONE_MSG_PASSWORD_OK, 0);
          temporary_enableheartbeat = enableheartbeat;
          if (enableheartbeat) hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_ENABLE,0);
          continue; // Нужно заново запустить select
        } else {
          fprintf(stderr, "Проверка не пройдена, либо пароль неправильный, либо протокол чужой\n");
          intro_works = 0;
          intro_buffer_p = 0;
          close(intro_socket_fd);
        };
      };
    };

    if (mode == BIND && FD_ISSET(server_socket_fd, &read_fd_kit)) {
      fprintf(stderr, "Входящее соединение, принимаю на пригласительный сокет\n");
      if (intro_works) {
        fprintf(stderr, "Предыдущий пригласительный сокет не отработал, заменяю на этот\n");
        ret = close(intro_socket_fd);
        if (ret) fprintf(stderr, "Предыдущий пригласительный сокет при закрытии сообщил о какой-то фигне\n");
        intro_works = 0;
        intro_buffer_p = 0;
      };
      intro_socket_fd = accept(server_socket_fd, NULL, NULL);
      if (intro_socket_fd == -1) fprintf(stderr, "Соединение не удалось\n");
      else {
        intro_works = 1;
        intro_buffer_p = 0;
      };
    };

    if (connection_works && FD_ISSET(connection_socket_fd, &read_fd_kit)) {
      //fprintf(stderr, "select чтение на звуковом сокете\n");
      if (download_header_buffer_p < sizeof(download_header_buffer)) {
        // читаем заголовок
        buffer_state = hphone_try_to_read(connection_socket_fd, download_header_buffer,
               &download_header_buffer_p, sizeof(download_header_buffer));
        if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
          connection_buffers_reset();
        };
        if (buffer_state == BUFFER_STATE_READY) {
          hphone_parse_header(download_header_buffer);
          if (!parsed_header.signature_ok) print_error_and_exit("Сигнатура не сходится");
          switch (parsed_header.msg_type) {
            case HIDDENPHONE_MSG_PASSWORD_OK:
            fprintf(stderr, "Пароль подошел\n");
            break;
            case HIDDENPHONE_MSG_SOUND:
            fprintf(stderr, "На подходе звуковое сообщение, длина %li байт\n", (long int) parsed_header.bodysize);
            break;
            case HIDDENPHONE_MSG_SOUND_ARRIVED:
            fprintf(stderr, "Звук доставлен\n");
            break;
            case HIDDENPHONE_MSG_HEARTBEAT:
            fprintf(stderr, "Пришел heartbeat, отвечаю\n");
            noheartbeat_count = 0;
            hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT_OK, 0);
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_OK:
            fprintf(stderr, "Пришел ответ на heartbeat\n");
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_PLEASE:
            fprintf(stderr, "Клиент просит отправить heartbeat, отправляю\n");
            hphone_send_header(0, HIDDENPHONE_MSG_HEARTBEAT, 0);
            break;
            case HIDDENPHONE_MSG_HEARTBEAT_ENABLE:
            fprintf(stderr, "Другой конец попросил включить heartbeat\n");
            temporary_enableheartbeat = 1;
            break;
            default:
            fprintf(stderr, "Неизвестный тип сообщения %li\n", (long int) parsed_header.msg_type);
            break;
          };

          if (parsed_header.bodysize > 0) {
            if (download_body_buffer) print_error_and_exit("Буфер не освобожден");
            download_body_buffer = malloc(parsed_header.bodysize + (FF_INPUT_BUFFER_PADDING_SIZE * 2));
            if (!download_body_buffer) print_error_and_exit("Память не выделяется");
            download_body_buffer_size = parsed_header.bodysize;
            download_body_additional_info_size = parsed_header.additional_size;
            download_message_type = parsed_header.msg_type;
            download_body_buffer_p = 0;
          } else {
            download_header_buffer_p = 0; // буфер готов к следующему заголовку
          };
        };
      } else {
        // читаем тело сообщения
        if (!download_body_buffer) print_error_and_exit("Буфера нет");
        buffer_state = hphone_try_to_read(connection_socket_fd, download_body_buffer,
               &download_body_buffer_p, download_body_buffer_size);
        if (buffer_state == BUFFER_STATE_ERROR_FD_CLOSED) {
          connection_buffers_reset();
        };
        if (buffer_state == BUFFER_STATE_READY) {
          if (download_message_type == HIDDENPHONE_MSG_SOUND) {
            //body_to_file(stdout ,download_body_buffer,
             // download_body_buffer_size, download_body_additional_info_size);
            decoded_sound = hphone_decode_buffer(download_body_buffer,
              download_body_buffer_size, download_body_additional_info_size);
            if (decoded_sound) {
              if (mp2save) save_to_mp2file(download_body_buffer, download_body_buffer_size,
                 download_body_additional_info_size);
              play_link_sound(decoded_sound, playback_device_name);
              discard_link_sound(&decoded_sound);
            } else fprintf(stderr, "Не получилось декодировать\n");
          };
          if (decoded_sound) discard_link_sound(decoded_sound);
          free(download_body_buffer);
          download_body_buffer = NULL;
          download_header_buffer_p = 0;
          download_body_buffer_size = 0;
        };
      };
    };

    if ((upload_queue_first != NULL) && FD_ISSET(connection_socket_fd, &write_fd_kit)) {
      //fprintf(stderr, "select запись на звуковом сокете\n");
      send_one_upload_page();
    };

    if (FD_ISSET(xlib_pipe[0], &read_fd_kit)) {
      read(xlib_pipe[0], &c, 1);
      if (c == 'B') {
        fprintf(stderr, "Нажата кнопка\n");
        if (upload_queue_first || !connection_works) {
          if (!connection_works) fprintf(stderr, "Соединения нет, отсечка\n");
          else fprintf(stderr, "Предыдущее сообщение еще не ушло, отсечка\n");
          continue;
        };
        encoded_sound_first_page = hphone_record_normalize_encode
            (xlib_pipe[0], record_device_name, &encoded_sound_size);
        if (!encoded_sound_first_page) {
          fprintf(stderr, "Отправление отменено\n");
          continue;
        };
        fprintf(stderr, "Отправляю\n");
        hphone_send_header(encoded_sound_size, HIDDENPHONE_MSG_SOUND, 0);
        insert_in_upload_queue(encoded_sound_first_page);
      } else fprintf(stderr, "Кнопка отпущена\n");
    };
  };

  return 0;
};

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ЕУ on 2018-05-01 17 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

Неплохой, чистый код.


Я устал. Я мухожук.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: spurdo on 2018-05-01 17 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

Работу программы не проверял. Мне кажется, чего-то подобного можно достичь при помощи tor, netcat и программы записи аудио.

Автору выражаю свое восхищение, зарываться в детали языка C ради довольно "высокоуровневой" задачи способен не каждый. Продолжайте в том же духе.


special-purpose undeground research and development organization
Зарегистрирован только на Рунионе. Связь только через ЛС форума.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ББ on 2018-05-01 17 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

ЕУ, spurdo, пожалуйста, посоветуйте язык программирования.
Меня устраивает Си, но конструкции проверок на ошибки и проверки указателей занимают много места.

Есть ли какой-нибудь защищенный аналог Си с защищенными указателями?

Си с плюсами? Не, не подходит.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: sup3r on 2018-05-01 18 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

тут, я так понимаю, реализован пуш2толк вида точка-точка через тор. вопрос в том, не лучше было бы реализовать архитектуру вида точка-сервер-точка? например, по аналогу джаббера.
тогда все будет выглядеть примерно так. арендуется любой сервер на который устанавливается "сервер" (аля джаббер сервер), на сервере "заводятся" учетные записи тех, кто будет общаться. в клиентах прописывается сервер, логин и пароль, клиенты "ходят на сервер" через тор, пакеты "ходят" так: клиент1-тор-сервер-тор-клиент2. Таким образом, можно реализовывать групповые секьюрные чаты, что сейчас, я так понимаю не столь тривиально реализовать, сервера могут быть одноразовые, что не уменьшает секьюрности.
А можно построить все на базе чего-то более-менее стандартизированного типа того же хмпп, или сипа поверх тора и получить мультиплатформенность и возможность использовать на тех же мобильных платформах, без нееобходимости писать свой софт (используя существующие клиенты сипа, или хмпп).

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: spurdo on 2018-05-01 18 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

ББ пишет:

Есть ли какой-нибудь защищенный аналог Си с защищенными указателями?

>аналог Си
>Си
>аналог

Деннис Ритчи в гробу переворачивается.

Если серьезно, то:

Под аналогом Си подразумеваю относительно низкоуровневые языки, со статической типизацией  (в смысле проверки типов при компиляции) и не слишком медленные.

Сейчас изучаю go, он же golang. Статическая типизация, никакой арифметики  с указателями, богатая стандартная библиотека, статическая линковка (на выходе один большой бинарник), встроенная поддержка конкуррентности в виде горутин (зеленых тредов), встроенный линтер исходников. Пока все нравится.

Есть еще rust, где обещается безопасность и абстракция без снижения производительности по сравнению с C. Довольно крутая кривая обучения (понятия ownership/borrowing/lifetime). Без сборщика мусора, чем интересен.

Java рекомендовать неудобно, но и не упомянуть нельзя.

Думаю, go попробовать стоит.


special-purpose undeground research and development organization
Зарегистрирован только на Рунионе. Связь только через ЛС форума.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ЕУ on 2018-05-01 18 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

Изучайте С++, очень удобный язык если уметь им пользоваться. Правда мало кто умеет, но это не умаляет достоинств языка.


Я устал. Я мухожук.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: spurdo on 2018-05-01 18 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

sup3r пишет:

А можно построить все на базе чего-то более-менее стандартизированного типа того же хмпп

sup3r пишет:

хмпп

Тоже подумал, когда дочитал до первого упоминание jabber в Вашем сообщении. XMPP-то расширяемый. Cisco на его основе чего только не понаделала.

sup3r пишет:

или сипа поверх тора

Я всегда встречал SIP как сигнализацию вместе с транспортом RTP (на основе UDP). Он с другими транспортами используется (не силен в телефонии)? Через Тор же только TCP проходит.

Редактировался spurdo (2018-05-01 18 ч.)


special-purpose undeground research and development organization
Зарегистрирован только на Рунионе. Связь только через ЛС форума.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: ЕУ on 2018-05-01 18 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

sup3r пишет:

вопрос в том, не лучше было бы реализовать архитектуру вида точка-сервер-точка? например, по аналогу джаббера

Не лучше, ибо излишнее усложнение, ухудшение скорости и безопасности без необходимости. Тор позволяет иметь прямые соединения с постоянными адресами, этим и нужно пользоваться.

А вообще, всё это уже придумали до нас. Есть TorChat и TorPhone.

Редактировался ЕУ (2018-05-01 18 ч.)


Я устал. Я мухожук.

[программка] HiddenPhone - голосовые сообщения через .onion  

  By: sup3r on 2018-05-01 19 ч.

Re: [программка] HiddenPhone - голосовые сообщения через .onion

ЕУ пишет:
sup3r пишет:

вопрос в том, не лучше было бы реализовать архитектуру вида точка-сервер-точка? например, по аналогу джаббера

Не лучше, ибо излишнее усложнение, ухудшение скорости и безопасности без необходимости. Тор позволяет иметь прямые соединения с постоянными адресами, этим и нужно пользоваться.

позвольте с Вами не согласиться. :)
если рассматривать софт чисто для своих нужд, и пользовать его только из под линя то, конечно, Ваш подход правильный и нормальный. Но, если предполагается возможность организации защищенного голосового общения на базе собственной инфраструктуры (сервера) (дабы соблюсти секьюрность) и предполагается пользование мобильными девайсами, то я бы смотрел в сторону защищенного общения по сипу/хмпп через тор, с использованием  своего сервера, но с использованием внешних софтовых клиентов, коих с открытым кодом в клирнете есть масса. Таким образом, экономим ресурсы и деньги на том, что не разрабатываем с нуля велосипед, в то же время, оставаясь достаточно секьюрными. как-то так.

за торчат и торфон спасибо, буду смотреть, что за звери такие.

 Вложения