Previous: Example 4, Up: Examples


13.5 Example 5

     /* smtp-server.c --- Example SMTP server with SASL authentication
      * Copyright (C) 2012 Simon Josefsson
      *
      * This file is part of GNU SASL.
      *
      * This program is free software: you can redistribute it and/or modify
      * it under the terms of the GNU General Public License as published by
      * the Free Software Foundation, either version 3 of the License, or
      * (at your option) any later version.
      *
      * This program is distributed in the hope that it will be useful,
      * but WITHOUT ANY WARRANTY; without even the implied warranty of
      * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      * GNU General Public License for more details.
      *
      * You should have received a copy of the GNU General Public License
      * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      *
      */
     
     /* This is a minimal SMTP server with GNU SASL authentication support.
        The only valid password is "sesam".  This server will complete
        authentications using LOGIN, PLAIN, DIGEST-MD5, CRAM-MD5, and
        SCRAM-SHA-1.  It accepts an optional command line parameter
        specifying the service name (i.e., a numerical port number or
        /etc/services name).  By default it listens on port "2000".  */
     
     #include <config.h>
     #include <string.h>
     #include <stdlib.h>
     #include <stdarg.h>
     #include <netdb.h>
     #include <signal.h>
     
     #include <gsasl.h>
     
     static int
     callback (Gsasl * ctx, Gsasl_session * sctx, Gsasl_property prop)
     {
       int rc = GSASL_NO_CALLBACK;
     
       switch (prop)
         {
         case GSASL_PASSWORD:
           gsasl_property_set (sctx, prop, "sesam");
           rc = GSASL_OK;
           break;
     
         default:
           /* You may want to log (at debug verbosity level) that an
              unknown property was requested here, possibly after filtering
              known rejected property requests. */
           break;
         }
     
       return rc;
     }
     
     static ssize_t
     gettrimline (char **line, size_t * n, FILE * fh)
     {
       ssize_t s = getline (line, n, fh);
     
       if (s >= 2)
         {
           if ((*line)[strlen (*line) - 1] == '\n')
     	(*line)[strlen (*line) - 1] = '\0';
           if ((*line)[strlen (*line) - 1] == '\r')
     	(*line)[strlen (*line) - 1] = '\0';
     
           printf ("C: %s\n", *line);
         }
     
       return s;
     }
     
     #define print(fh, ...)							\
       printf ("S: "), printf (__VA_ARGS__), fprintf (fh, __VA_ARGS__)
     
     static void
     server_auth (FILE * fh, Gsasl_session * session)
     {
       char *line = NULL;
       size_t n = 0;
       char *p;
       int rc;
     
       /* The ordering and the type of checks in the following loop has to
          be adapted for each protocol depending on its SASL properties.
          SMTP is a "server-first" SASL protocol.  This implementation do
          not support piggy-backing of the initial client challenge nor
          piggy-backing of the terminating server response.  See RFC 2554
          and RFC 4422 for terminology.  That profile results in the
          following loop structure.  Ask on the help-gsasl list if you are
          uncertain.  */
       do
         {
           rc = gsasl_step64 (session, line, &p);
           if (rc == GSASL_NEEDS_MORE || (rc == GSASL_OK && p && *p))
     	{
     	  print (fh, "334 %s\n", p);
     	  gsasl_free (p);
     
     	  if (gettrimline (&line, &n, fh) < 0)
     	    {
     	      print (fh, "221 localhost getline failure\n");
     	      goto done;
     	    }
     	}
         }
       while (rc == GSASL_NEEDS_MORE);
     
       if (rc != GSASL_OK)
         {
           print (fh, "535 gsasl_step64 (%d): %s\n", rc, gsasl_strerror (rc));
           goto done;
         }
     
       {
         const char *authid = gsasl_property_fast (session, GSASL_AUTHID);
         const char *authzid = gsasl_property_fast (session, GSASL_AUTHZID);
         print (fh, "235 OK [authid: %s authzid: %s]\n",
     	   authid ? authid : "N/A", authzid ? authzid : "N/A");
       }
     
     done:
       free (line);
     }
     
     static void
     smtp (FILE * fh, Gsasl * ctx)
     {
       char *line = NULL;
       size_t n = 0;
       int rc;
     
       print (fh, "220 localhost ESMTP GNU SASL smtp-server\n");
     
       while (gettrimline (&line, &n, fh) >= 0)
         {
           if (strncmp (line, "EHLO ", 5) == 0 || strncmp (line, "ehlo ", 5) == 0)
     	{
     	  char *mechlist;
     
     	  rc = gsasl_server_mechlist (ctx, &mechlist);
     	  if (rc != GSASL_OK)
     	    {
     	      print (fh, "221 localhost gsasl_server_mechlist (%d): %s\n",
     		     rc, gsasl_strerror (rc));
     	      goto done;
     	    }
     
     	  print (fh, "250-localhost\n");
     	  print (fh, "250 AUTH %s\n", mechlist);
     
     	  gsasl_free (mechlist);
     	}
           else if (strncmp (line, "AUTH ", 5) == 0
     	       || strncmp (line, "auth ", 5) == 0)
     	{
     	  Gsasl_session *session = NULL;
     
     	  if ((rc = gsasl_server_start (ctx, line + 5, &session)) != GSASL_OK)
     	    {
     	      print (fh, "221 localhost gsasl_server_start (%d): %s\n",
     		     rc, gsasl_strerror (rc));
     	      goto done;
     	    }
     
     	  server_auth (fh, session);
     
     	  gsasl_finish (session);
     	}
           else if (strncmp (line, "QUIT", 4) == 0
     	       || strncmp (line, "quit", 4) == 0)
     	{
     	  print (fh, "221 localhost QUIT\n");
     	  goto done;
     	}
           else
     	print (fh, "500 unrecognized command\n");
         }
     
       print (fh, "221 localhost getline failure\n");
     
     done:
       free (line);
     }
     
     int
     main (int argc, char *argv[])
     {
       const char *service = argc > 1 ? argv[1] : "2000";
       volatile int run = 1;
       struct addrinfo hints, *addrs;
       int sockfd;
       int rc;
       int yes = 1;
       Gsasl *ctx;
     
       setvbuf (stdout, NULL, _IONBF, 0);
     
       rc = gsasl_init (&ctx);
       if (rc < 0)
         {
           printf ("gsasl_init (%d): %s\n", rc, gsasl_strerror (rc));
           exit (EXIT_FAILURE);
         }
     
       printf ("%s [gsasl header %s library %s]\n",
     	  argv[0], GSASL_VERSION, gsasl_check_version (NULL));
     
       gsasl_callback_set (ctx, callback);
     
       memset (&hints, 0, sizeof (hints));
       hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
       hints.ai_socktype = SOCK_STREAM;
     
       rc = getaddrinfo (NULL, service, &hints, &addrs);
       if (rc < 0)
         {
           printf ("getaddrinfo: %s\n", gai_strerror (rc));
           exit (EXIT_FAILURE);
         }
     
       sockfd = socket (addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
       if (sockfd < 0)
         {
           perror ("socket");
           exit (EXIT_FAILURE);
         }
     
       if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes)) < 0)
         {
           perror ("setsockopt");
           exit (EXIT_FAILURE);
         }
     
       rc = bind (sockfd, addrs->ai_addr, addrs->ai_addrlen);
       if (rc < 0)
         {
           perror ("bind");
           exit (EXIT_FAILURE);
         }
     
       freeaddrinfo (addrs);
     
       rc = listen (sockfd, SOMAXCONN);
       if (rc < 0)
         {
           perror ("listen");
           exit (EXIT_FAILURE);
         }
     
       signal (SIGPIPE, SIG_IGN);
     
       while (run)
         {
           struct sockaddr from;
           socklen_t fromlen = sizeof (from);
           char host[NI_MAXHOST];
           int fd;
           FILE *fh;
     
           fd = accept (sockfd, &from, &fromlen);
           if (fd < 0)
     	{
     	  perror ("accept");
     	  continue;
     	}
     
           rc = getnameinfo (&from, fromlen, host, sizeof (host),
     			NULL, 0, NI_NUMERICHOST);
           if (rc == 0)
     	printf ("connection from %s\n", host);
           else
     	printf ("getnameinfo: %s\n", gai_strerror (rc));
     
           fh = fdopen (fd, "w+");
           if (!fh)
     	{
     	  perror ("fdopen");
     	  close (fd);
     	  continue;
     	}
     
           smtp (fh, ctx);
     
           fclose (fh);
         }
     
       close (sockfd);
       gsasl_done (ctx);
     
       return 0;
     }