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
[hmendes: arquivos_pki]$ openssl req -new -x509 -key chave_privada_ca.pem -out certificado_ca.pem -days 365
# "carbona", a partir do par de chaves anteriormente gerado.
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
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 1500int 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);
}
}