http://www.pedro.jmrezende.com.br/sd.htm > Algoritmos: OpenSSL

Usando OpenSSL

Uma implementação livre do protocolo SSL

Trabalho da Disciplina Segurança de Dados 1/03

Hammurabi das Chagas Mendes
Universidade de Brasília
13 de Julho de 2003


Seção 1: O protocolo SSL e o toolkit OpenSSL

O protocolo SSL foi criado com o objetivo de proporcionar mecanismos de autenticação e sigilo entre duas aplicações que se comunicam via algum protocolo de comunicação (por exemplo, o TCP/IP). Outros aspectos importantes considerados no momento de sua concepção foram: interoperabilidade, permitindo a comunicação com outra aplicação sem que haja a necessidade de entrar em detalhes a respeito de sua implementação; extensiblidade, que permite criar novas rotinas e funcionalidades baseadas em mecanismos pré-existentes do protocolo; por fim, eficiência, tornando o protocolo viável para o uso entre aplicações cliente-servidor via Internet.
A arquitetura do SSL é disposta em camadas, a exemplo do TCP/IP. Uma delas, a chamada Record Layer, recebe informações não encriptadas das aplicações, dispondo-as em blocos numerados seqüencialmente. Estes blocos então passam por uma compressão, seguida da geração de códigos de autenticação (MACs). Em seguida, os blocos são encriptados e enviados. A numeração das mensagens enviadas é importante para facilitar o trabalho do receptor na detecção de blocos em falta, alterados ou injetados por terceiros.
Os protocolos sobre os quais o SSL é construído incluem um especialmente concebido com o objetivo de sinalizar transações entre estratégias de cifragem usadas na sessão. Este protocolo é denominado Change Cipher Spec Protocol. Outro protocolo importante é o Alert Protocol, usado na sinalização de erros e em notificações de fechamento de conexão.
Ainda há o Handshake Protocol, que estabelece os parâmetros criptográficos da sessão, operando ao topo da Record Layer. No início da sessão, o cliente envia ao servidor uma hello message, informando os algoritmos e protocolos disponibilizados por sua implementação do SSL, sendo eles criptográficos ou não (por exemplo, os algoritmos de compressão associados também são informados). O servidor, baseado nos algoritmos e protocolos que a ele estão disponíveis, escolhe alguns dos parâmetros informados pelo cliente para serem usados no estabelecimento da sessão, notificando-o através de uma outra hello message.
Em seguida, o servidor envia seu certificado (ou informações associadas a um protocolo usado para troca de chaves, caso ele não tenha um certificado ou seu certificado possa ser usado apenas para verificar assinaturas digitais), e o cliente opcionalmente faz o mesmo. Logo após, a chave de sessão é instituída, através dos métodos criptográficos estabelecidos na troca das hello messages (por exemplo, Diffie-Hellmann ou RSA).
O projeto OpenSSL disponibiliza um toolkit em código livre, que implementa o protocolo SSL e vários algoritmos e primitivas criptográficas de uso comum, incluindo algoritmos de troca de chaves, funções de hash, algoritmos simétricos e assimétricos. O toolkit se apesenta na forma de duas bibliotecas e um conjunto de programas que implementam as rotinas por elas disponibilizadas. Os mecanismos do SSL estão implementadas na libssl, e os outros algoritmos estão implmentados na libcrypto.

Seção 2: Usando as bibliotecas OpenSSL

Nesta seção será demonstrada a implementação de um mini servidor web, que suporta SSL. Todo o desenvolvimento e os testes foram feitos sobre o sistema operacional FreeBSD 4.7, porém o servidor deve requerer poucas (talvez nenhuma) modificação para compilar sobre outros sistemas UNIX-like (incluindo Linux). Nesta seção, serão mostradas as partes mais relevantes desta implementação. Para referência completa ao código, vide Apêndice A.

O servidor, antes de aceitar conexões oriundas dos clientes, inicializa uma estrutura interna responsável por acondicionar seu certificado, a chave privada correspondente e os certificados das autoridades certificadoras nas quais ele confia. Esta estrutura, denominada "contexto SSL", é especialmente importante para evitar que estas informações sejam constantemente carregadas na criação de cada sessão SSL que eventualmente surja. O código correspondente é mostrado adiante, explicitando a criação do contexto com SSL_CTX_new(), referente a sessões SSL versão 2 ou 3 (denotadas por SSL_v23_method()).

metodo = SSLv23_method();
contexto = SSL_CTX_new(metodo);

if(SSL_CTX_use_certificate_chain_file(contexto, ARQUIVO_CERTIFICADOS) != 1) {
  ERR_print_errors(bio_stderr);
  return NULL;
}

SSL_CTX_set_default_passwd_cb(contexto, pegar_senha);

if(SSL_CTX_use_PrivateKey_file(contexto, ARQUIVO_CHAVE_PRIVADA, SSL_FILETYPE_PEM) != 1) {
  ERR_print_errors(bio_stderr);
  return NULL;
}

if(SSL_CTX_load_verify_locations(contexto, ARQUIVO_CAS, NULL) != 1) {
  ERR_print_errors(bio_stderr);
  return NULL;
}

O certificado do servidor (e os certificados que o assinam recursivamente, até a raiz) são carregados no contexto através de uma chamada a SSL_CTX_use_certificate_chain_file(). A chave privada correspondente é carregada através da rotina SSL_CTX_use_PrivateKey_file(), de modo análogo. Caso a chave privada esteja protegida por uma senha, a rotina que a obtém deve ser especificada em SSL_CTX_set_default_passwd_cb(). Por fim, as autoridades certificadoras que têm a confiança do servidor são carregadas por SSL_CTX_load_verify_locations().

O programa aloca a porta 10000/tcp ao entrar em operação, esperando a abertura de conexões através de uma chamada blocante a accept(). É de praxe que, quando se implementa este tipo de aplicação, seja criado um novo processo assim que uma nova conexão é feita por um cliente, permitindo que o servidor esteja novamente aceitando novos pedidos sem ter que esperar pelo fim de toda sessão que a nova conexão representa. O servidor aqui apresentado implementa este paradigma.

while(1) {
  if((socket_novo = accept(...)) < 0) {
    if(errno == EINTR) {
      continue;
    }
  }

  if(!(pid = fork())) {
    SSL *ssl;
    BIO *bio_socket_novo;

    bio_socket_novo = BIO_new_socket(socket_novo, BIO_NOCLOSE);

    ssl = SSL_new(contexto);
    SSL_set_bio(ssl, bio_socket_novo, bio_socket_novo);

    if(SSL_accept(ssl) != 1) {
      ERR_print_errors(bio_stderr);
      return EXIT_FAILURE;
    }

    if(sessao_http(ssl, socket_novo) == -1) {
      fprintf(stderr, "Erro na sessão HTTP\n");
      return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
  }

  close(socket_novo);
}

Assim que um processo filho assume a conexão, ele faz uso da rotina SSL_new(), que cria uma sessão SSL a partir do contexto previamente criado. Logo após, é usada a rotina SSL_accept(), que representa o lado do servidor do protocolo de Handshake. Neste ponto, a sessão HTTP segura pode ser estabelecer.

bio_ssl = BIO_new(BIO_f_ssl());
BIO_set_ssl(bio_ssl, ssl, BIO_CLOSE);

bio_buffer_ssl = BIO_new(BIO_f_buffer());
BIO_push(bio_buffer_ssl, bio_ssl);

fim_pedido = 0;
while(!fim_pedido) {
  retorno = BIO_gets(bio_buffer_ssl, buffer, TAMANHO_BUFFER - 1);

  if(SSL_get_error(ssl, retorno) != SSL_ERROR_NONE) {
    ERR_print_errors(bio_stderr);
    return -1;
  }

  if(!strcmp(buffer, "\r\n") || !strcmp(buffer, "\n")) {
    fim_pedido = 1;
  }

  if(inicio_nome_arquivo = strstr(buffer, "GET")) {
    inicio_nome_arquivo += 5;
    fim_nome_arquivo = strstr(inicio_nome_arquivo, " ");
    *fim_nome_arquivo = '\0';
    if(!strcmp(inicio_nome_arquivo, "")) {
      inicio_nome_arquivo = "index.html";
    }
    printf("Arquivo solicitado: %s\n", inicio_nome_arquivo);
    if(transmitir_arquivo(inicio_nome_arquivo, bio_buffer_ssl) == -1) {
      return -1;
    }
  }
}

if(!SSL_shutdown(ssl)) {
  shutdown(socket_novo, SHUT_WR);
  SSL_shutdown(ssl);
}

SSL_free(ssl);

Antes de se iniciar a sessão HTTP, o servidor usa a rotina BIO_new(), que retorna uma estrutura especial que intermedia as operações de I/O na sessão SSL. A vantagem de se usar tal estrutura é que no momento de sua criação há a possiblidade de se especificar métodos especiais de funcionamento de suas rotinas de entrada e saída, entre elas, um método que implementa a funcionalidade da Record Layer, fazendo com que todas as rotinas do protocolo tornem-se transparentes à aplicação. Este método é especificado com auxílio da rotina BIO_f_ssl(). Tudo o que o servidor precisa fazer é então implementar um parser para as mensagens do protocolo HTTP. No caso do servidor aqui demonstrado, o parser apenas interpreta pedidos feitos através da primitiva GET, e retorna o arquivo solicitado ao cliente. Após a transmissão, a conexão com o cliente é fechada (SSL_shutdown()) e a sessão SSL é destruída com SSL_free().

Seção 3: Certificados Digitais

Foram criados três certificados digitais para o teste do programa, sendo um deles auto-assinado. Para criar os certificados, foi usado o programa openssl, disponibilzado pelo próprio toolkit. Este programa implementa os algoritmos criptográficos disponibilizados pela libcrypto, permitindo realizar inúmeras operações via linha-de-comando.

Inicialmente, foi criado um certificado para o servidor, no nome de "servidor", assinado por um certificado auto-assinado no nome de "carbona" (o hostname máquina do autor). Os comandos do openssl correspondentes são:

# Gera um par de chaves RSA, para a autoridade certificadora "carbona".
# O arquivo de destino contém a chave privada e as informações
# necessárias para a geração da chave pública correspondente.

[hmendes: arquivos_pki]$ openssl genrsa -des3 -out chave_privada_ca.pem 2048
warning, not much extra random data, consider using the -rand option
Generating RSA private key, 2048 bit long modulus
.............+++
......+++
e is 65537 (0x10001)
Enter PEM pass phrase:
Verifying password - Enter PEM pass phrase:
[hmendes: arquivos_pki]$

# Gera um certificado auto-assinado para a autoridade certificadora
# "carbona", a partir do par de chaves anteriormente gerado.

[hmendes: arquivos_pki]$ openssl req -new -x509 -key chave_privada_ca.pem -out certificado_ca.pem -days 365
Using configuration from /etc/ssl/openssl.cnf
Enter PEM pass phrase:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:BR
State or Province Name (full name) [Some-State]:Distrito Federal
Locality Name (eg, city) []:Brasília
Organization Name (eg, company) [Internet Widgits Pty Ltd]:carbona
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:carbona
Email Address []:
[hmendes: arquivos_pki]$

# Gera um par de chaves RSA, para o servidor.
# O arquivo de destino contém a chave privada e as informações
# necessárias para a geração da chave pública correspondente.

[hmendes: arquivos_pki]$ openssl genrsa -des3 -out chave_privada_sv.pem 2048
warning, not much extra random data, consider using the -rand option
Generating RSA private key, 2048 bit long modulus
...........................+++
....................................................+++
e is 65537 (0x10001)
Enter PEM pass phrase:
Verifying password - Enter PEM pass phrase:
[hmendes: arquivos_pki]$

# Gera uma solicitação de certificado a ser assinado
# pela autoridade certificadora "carbona".
# A solicitação está no nome de "servidor".

[hmendes: arquivos_pki]$ openssl req -new -key chave_privada_sv.pem -out pedido_sv.pem
Using configuration from /etc/ssl/openssl.cnf
Enter PEM pass phrase:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:BR
State or Province Name (full name) [Some-State]:Distrito Federal
Locality Name (eg, city) []:Brasília
Organization Name (eg, company) [Internet Widgits Pty Ltd]:servidor
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:servidor
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
[hmendes: arquivos_pki]$

# A autoridade certificadora "carbona" assina
# a solicitação de certificado feita pelo servidor.
# O certificado está no nome de "servidor".

[hmendes: arquivos_pki]$ openssl ca -config ca.config -out certificado_sv.pem -in pedido_sv.pem
Using configuration from ca.config
Enter PEM pass phrase:
Check that the request matches the signature
Signature ok
The Subjects Distinguished Name is as follows
countryName           :PRINTABLE:'BR'
stateOrProvinceName   :PRINTABLE:'Distrito Federal'
localityName          :T61STRING:'Bras\0xFFFFFFEDlia'
organizationName      :PRINTABLE:'servidor'
commonName            :PRINTABLE:'servidor'
Certificate is to be certified until Jul 13 03:31:01 2004 GMT (365 days)
Sign the certificate? [y/n]:y
 

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
[hmendes: arquivos_pki]$

O arquivo de configuração ca.config estabelece uma hierarquia de diretórios usada para armazenar as informações acerca da autoridade certificadora hipotética criada no contexto deste trabalho, além de prover dados adicionais a respeito das características que os certificados a serem assinados por ela devem possuir. Ele é distribuído juntamente com o servidor e os certificados correspondentes.

Após o primeiro teste, foi necessário criar outro certificado para o servidor, feito de forma absolutamente semelhante à criação do primeiro. Os detalhes do problema que resultou na nova criação estão descritos na seção seguinte.

Seção 4: Testes

Logo que o servidor entra em execução, ele solicita ao usuário a senha que protege a chave privada que corresponde ao seu certificado. A senha é então inserida (mostrada aqui às claras), e ele está pronto para receber novas conexões.

Assim que se abre a conexão com o servidor, através do navegador Mozilla 1.3.1, surge a informação de que o certificado do servidor não pode ser validado porque o certificado que o emitiu não é considerado confiável.

A solução é importar o certificado raiz "carbona" para o Mozilla:

Ao tentar novamente, o certificado é validado, porém é informado ao usuário que o domínio no qual o certificado se refere não bate com o domínio que foi efetivamente acessado pelo usuário (o servidor roda em "localhost", mas o certificado se refere a "servidor").

Tendo em vista resolver este problema, é confeccionado outro certificado, que é feito no nome de "localhost", o domínio no qual o servidor vai rodar. Desta vez, a conexão se estabelece sem quaisquer erros:

Referências

    1. Página Oficial do projeto OpenSSL: http://www.openssl.org
    2. The SSL Protocol, Version 3.0: http://wp.netscape.com/eng/ssl3/draft302.txt
    3. Stevens, Richard: Unix Network Programming, volume 1, 2nd edition.
    4. An Introduction to OpenSSL Programming (Part I): http://www.rtfm.com/openssl-examples/part1.pdf

    Apêndice A

Segue abaixo a referência integral para o código fonte do programa servidor desenvolvido neste trabalho.
 
Arquivo servidor.c:

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

#include "utils.h"

#define PORTA 10000
#define TAMANHO_BUFFER 1500

int sessao_http(SSL *ssl, int socket_novo);
int transmitir_arquivo(char *nome_arquivo, BIO *bio_buffer_ssl);

int main(int argc, char **argv) {
  int socket_listen, socket_novo;
  struct sockaddr_in endereco_local, endereco_remoto;
  int tamanho_sockaddr_in;
  pid_t pid;
  SSL_CTX *contexto;

  if((socket_listen = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("Falha ao tentar criar socket");
    exit(EXIT_FAILURE);
  }

  memset(&endereco_local, 0, sizeof(struct sockaddr_in));
  endereco_local.sin_addr.s_addr = htonl(INADDR_ANY);
  endereco_local.sin_family = AF_INET;
  endereco_local.sin_port = htons(PORTA);

  tamanho_sockaddr_in = sizeof(struct sockaddr_in);

  if(bind(socket_listen, (struct sockaddr *) &endereco_local, tamanho_sockaddr_in) < 0) {
    perror("Falha ao tentar alocar endereço local");
    exit(EXIT_FAILURE);
  }

  if(listen(socket_listen, 1) < 0) {
    perror("Falha ao tentar iniciar serviço");
    exit(EXIT_FAILURE);
  }

  if(armar_sinais() == -1) {
    exit(EXIT_FAILURE);
  }

  inicializar_biblioteca();
  contexto = inicializar_contexto();

  while(1) {
    if((socket_novo = accept(socket_listen, (struct sockaddr *) &endereco_remoto, &tamanho_sockaddr_in)) < 0) {
      if(errno == EINTR) {
        continue;
      }
      perror("Falha ao tentar aceitar uma nova conexão");
      exit(EXIT_FAILURE);
    }

    if(!(pid = fork())) {
      SSL *ssl;
      BIO *bio_socket_novo;

      bio_socket_novo = BIO_new_socket(socket_novo, BIO_NOCLOSE);

      ssl = SSL_new(contexto);
      SSL_set_bio(ssl, bio_socket_novo, bio_socket_novo);

      if(SSL_accept(ssl) != 1) {
        ERR_print_errors(bio_stderr);
        return EXIT_FAILURE;
      }

      if(sessao_http(ssl, socket_novo) == -1) {
        fprintf(stderr, "Erro na sessão HTTP\n");
        return EXIT_FAILURE;
      }

      return EXIT_SUCCESS;
    }

    close(socket_novo);
  }

  return EXIT_SUCCESS;
}

int sessao_http(SSL *ssl, int socket_novo) {
  char buffer[TAMANHO_BUFFER];
  int fim_pedido, retorno;
  BIO *bio_buffer_ssl, *bio_ssl;
  char *inicio_nome_arquivo, *fim_nome_arquivo;

  bio_ssl = BIO_new(BIO_f_ssl());
  BIO_set_ssl(bio_ssl, ssl, BIO_CLOSE);

  bio_buffer_ssl = BIO_new(BIO_f_buffer());
  BIO_push(bio_buffer_ssl, bio_ssl);

  fim_pedido = 0;
  while(!fim_pedido) {
    retorno = BIO_gets(bio_buffer_ssl, buffer, TAMANHO_BUFFER - 1);

    if(SSL_get_error(ssl, retorno) != SSL_ERROR_NONE) {
      ERR_print_errors(bio_stderr);
      return -1;
    }

    if(!strcmp(buffer, "\r\n") || !strcmp(buffer, "\n")) {
      fim_pedido = 1;
    }

    if(inicio_nome_arquivo = strstr(buffer, "GET")) {
      inicio_nome_arquivo += 5;
      fim_nome_arquivo = strstr(inicio_nome_arquivo, " ");
      *fim_nome_arquivo = '\0';

      if(!strcmp(inicio_nome_arquivo, "")) {
        inicio_nome_arquivo = "index.html";
      }
      printf("Arquivo solicitado: %s\n", inicio_nome_arquivo);
      if(transmitir_arquivo(inicio_nome_arquivo, bio_buffer_ssl) == -1) {
        return -1;
      }
    }
  }

  if(!SSL_shutdown(ssl)) {
    shutdown(socket_novo, SHUT_WR);
    SSL_shutdown(ssl);
  }

  SSL_free(ssl);
  close(socket_novo);
  return 0;
}

int transmitir_arquivo(char *nome_arquivo, BIO *bio_buffer_ssl) {
  FILE *arquivo_entrada;
  char buffer[TAMANHO_BUFFER];
  int quantidade_lida;

  if(!(arquivo_entrada = fopen(nome_arquivo, "r"))) {
    if(BIO_puts(bio_buffer_ssl, "HTTP/1.0 404 Not Found\r\n\r\n") <= 0) {
      ERR_print_errors(bio_stderr);
      return -1;
    }

    if(BIO_puts(bio_buffer_ssl, "Arquivo não encontrado\r\n") <= 0) {
      ERR_print_errors(bio_stderr);
      return -1;
    }

    if(BIO_flush(bio_buffer_ssl) < 0) {
      ERR_print_errors(bio_stderr);
      return -1;
    }

    return 0;
  }

  if(BIO_puts(bio_buffer_ssl, "HTTP/1.0 200 OK\r\n\r\n") <= 0) {
    ERR_print_errors(bio_stderr);
    return -1;
  }

  while(quantidade_lida = fread(buffer, sizeof(char), TAMANHO_BUFFER - 1, arquivo_entrada)) {
    buffer[quantidade_lida] = '0';

    if(BIO_write(bio_buffer_ssl, buffer, quantidade_lida) < quantidade_lida) {
      ERR_print_errors(bio_stderr);
      return -1;
    }
  }

  if(BIO_flush(bio_buffer_ssl) < 0) {
    ERR_print_errors(bio_stderr);
    return -1;
  }

  fclose(arquivo_entrada);
  return 0;
}

Arquivo utils.h:

#ifndef UTILS_H
#define UTILS_H

#include <openssl/ssl.h>
#include <openssl/bio.h>

extern BIO *bio_stderr;

void inicializar_biblioteca(void);
SSL_CTX *inicializar_contexto(void);
int armar_sinais(void);

#endif /* UTILS_H */

Arquivo utils.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "utils.h"

#define ARQUIVO_CERTIFICADOS "arquivos_pki/certificado_lh.pem"
#define ARQUIVO_CHAVE_PRIVADA "arquivos_pki/chave_privada_lh.pem"
#define ARQUIVO_CAS "arquivos_pki/certificado_ca.pem"

BIO *bio_stderr;

int pegar_senha(char *buffer, int tamanho, int rwflag, void *dados);
void tratador_sinais(int sinal);

void inicializar_biblioteca(void) {
  bio_stderr = BIO_new_fp(stderr, BIO_NOCLOSE);

  SSL_library_init();
  SSL_load_error_strings();
}

SSL_CTX *inicializar_contexto(void) {
  SSL_METHOD *metodo;
  SSL_CTX *contexto;

  metodo = SSLv23_method();
  contexto = SSL_CTX_new(metodo);

  if(SSL_CTX_use_certificate_chain_file(contexto, ARQUIVO_CERTIFICADOS) != 1) {
    ERR_print_errors(bio_stderr);
    return NULL;
  }

  SSL_CTX_set_default_passwd_cb(contexto, pegar_senha);

  if(SSL_CTX_use_PrivateKey_file(contexto, ARQUIVO_CHAVE_PRIVADA, SSL_FILETYPE_PEM) != 1) {
    ERR_print_errors(bio_stderr);
    return NULL;
  }

  if(SSL_CTX_load_verify_locations(contexto, ARQUIVO_CAS, NULL) != 1) {
    ERR_print_errors(bio_stderr);
    return NULL;
  }

  return contexto;
}

int pegar_senha(char *buffer, int tamanho, int rwflag, void *dados) {
  int posicao_ultimo_caractere;

  fputs("Digite a senha da parte privada do certificado: ", stdout);
  fgets(buffer, tamanho, stdin);

  posicao_ultimo_caractere = strlen(buffer) - 1;

  if(buffer[posicao_ultimo_caractere] == '\n') {
    buffer[posicao_ultimo_caractere] = '\0';
  }

  return strlen(buffer);
}

int armar_sinais(void) {
  struct sigaction info_sinal;
  sigset_t sinais_bloqueados;

  memset(&info_sinal, 0, sizeof(struct sigaction));
  sigemptyset(&sinais_bloqueados);

  sigaddset(&sinais_bloqueados, SIGTERM);
  info_sinal.sa_mask = sinais_bloqueados;
  info_sinal.sa_handler = tratador_sinais;
  info_sinal.sa_flags = SA_NOCLDSTOP;

  if(sigaction(SIGCHLD, &info_sinal, NULL) == -1) {
    perror("Erro ao armar função tratadora de SIGCHLDs");
    return -1;
  }

  memset(&info_sinal, 0, sizeof(struct sigaction));
  sigemptyset(&sinais_bloqueados);

  info_sinal.sa_mask = sinais_bloqueados;
  info_sinal.sa_handler = tratador_sinais;

  if(sigaction(SIGTERM, &info_sinal, NULL) == -1) {
    perror("Erro ao armar função tratadora de SIGTERMs");
    return -1;
  }

  memset(&info_sinal, 0, sizeof(struct sigaction));
  sigemptyset(&sinais_bloqueados);

  info_sinal.sa_mask = sinais_bloqueados;
  info_sinal.sa_handler = tratador_sinais;

  if(sigaction(SIGPIPE, &info_sinal, NULL) == -1) {
    perror("Erro ao armar função tratadora de SIGPIPEs");
    return -1;
  }

  return 0;
}

void tratador_sinais(int sinal) {
  int pid, status;

  if(sinal == SIGTERM) {
    puts("Saindo do programa");
    exit(EXIT_SUCCESS);
  }
  if(sinal == SIGCHLD) {
    while((pid = waitpid(-1, &status, WNOHANG)) > 0);
  }
}