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

#define KEYFILE "key.pem"
#define CERTFILE "cert.pem"
#define CAFILE "ca.pem"
#define CRLFILE NULL

/* This is a sample TLS 1.0 echo server.
 */


#define SA struct sockaddr
#define SOCKET_ERR(err,s) if(err==-1) {perror(s);return(1);}
#define MAX_BUF 1024
#define PORT 5556               /* listen to 5556 port */
#define DH_BITS 1024

/* These are global */
gnutls_certificate_server_credentials x509_cred;

gnutls_session initialize_tls_session()
{
   gnutls_session session;

   gnutls_init(&session, GNUTLS_SERVER);

   /* avoid calling all the priority functions, since the defaults
    * are adequate.
    */
   gnutls_set_default_priority( session);   

   gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);

   /* request client certificate if any.
    */
   gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST);

   gnutls_dh_set_prime_bits( session, DH_BITS);

   /* some broken clients may require this in order to connect. 
    * This may weaken security though.
    */
   /* gnutls_handshake_set_rsa_pms_check( session, 1); */

   
   return session;
}

gnutls_dh_params dh_params;

static int generate_dh_params(void) {
gnutls_datum prime, generator;

   /* Generate Diffie Hellman parameters - for use with DHE
    * kx algorithms. These should be discarded and regenerated
    * once a day, once a week or once a month. Depends on the
    * security requirements.
    */
   gnutls_dh_params_init( &dh_params);
   gnutls_dh_params_generate( &prime, &generator, DH_BITS);
   gnutls_dh_params_set( dh_params, prime, generator, DH_BITS);

   free( prime.data);
   free( generator.data);
   
   return 0;
}

int main()
{
   int err, listen_sd, i;
   int sd, ret;
   struct sockaddr_in sa_serv;
   struct sockaddr_in sa_cli;
   int client_len;
   char topbuf[512];
   gnutls_session session;
   char buffer[MAX_BUF + 1];
   int optval = 1;
   char name[256];

   strcpy(name, "Echo Server");

   /* this must be called once in the program
    */
   gnutls_global_init();

   gnutls_certificate_allocate_credentials(&x509_cred);
   gnutls_certificate_set_x509_trust_file(x509_cred, CAFILE, 
      GNUTLS_X509_FMT_PEM);

   gnutls_certificate_set_x509_key_file(x509_cred, CERTFILE, KEYFILE, 
      GNUTLS_X509_FMT_PEM);

   generate_dh_params();
   
   gnutls_certificate_set_dh_params( x509_cred, dh_params);

   /* Socket operations
    */
   listen_sd = socket(AF_INET, SOCK_STREAM, 0);
   SOCKET_ERR(listen_sd, "socket");

   memset(&sa_serv, '\0', sizeof(sa_serv));
   sa_serv.sin_family = AF_INET;
   sa_serv.sin_addr.s_addr = INADDR_ANY;
   sa_serv.sin_port = htons(PORT);  /* Server Port number */

   setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

   err = bind(listen_sd, (SA *) & sa_serv, sizeof(sa_serv));
   SOCKET_ERR(err, "bind");
   err = listen(listen_sd, 1024);
   SOCKET_ERR(err, "listen");

   printf("%s ready. Listening to port '%d'.\n\n", name, PORT);

   client_len = sizeof(sa_cli);
   for (;;) {
      session = initialize_tls_session();

      sd = accept(listen_sd, (SA *) & sa_cli, &client_len);

      printf("- connection from %s, port %d\n",
             inet_ntop(AF_INET, &sa_cli.sin_addr, topbuf,
                       sizeof(topbuf)), ntohs(sa_cli.sin_port));

      gnutls_transport_set_ptr( session, sd);
      ret = gnutls_handshake( session);
      if (ret < 0) {
         close(sd);
         gnutls_deinit(session);
         fprintf(stderr, "*** Handshake has failed (%s)\n\n",
                 gnutls_strerror(ret));
         continue;
      }
      printf("- Handshake was completed\n");

      /* see the Getting peer's information example */
      /* print_info(session); */

      i = 0;
      for (;;) {
         bzero(buffer, MAX_BUF + 1);
         ret = gnutls_record_recv( session, buffer, MAX_BUF);

         if (ret == 0) {
            printf
                ("\n- Peer has closed the GNUTLS connection\n");
            break;
         } else if (ret < 0) {
            fprintf(stderr,
                    "\n*** Received corrupted data(%d). Closing the connection.\n\n",
                    ret);
            break;
         } else if (ret > 0) {
            /* echo data back to the client
             */
            gnutls_record_send( session, buffer,
                         strlen(buffer));
         }
      }
      printf("\n");
      gnutls_bye( session, GNUTLS_SHUT_WR); /* do not wait for
                                 * the peer to close the connection.
                                 */

      close(sd);
      gnutls_deinit(session);

   }
   close(listen_sd);

   gnutls_certificate_free_credentials(x509_cred);

   gnutls_global_deinit();

   return 0;

}

