Next: , Previous: , Up: Top   [Contents][Index]


7 How to develop an Assuan server

Implementing a server for Assuan is a bit more complex than a client. However, it is a straightforward task we are going to explain using a commented example.

The list of the implemented server commands is defined by a table like:

  static struct {
    const char *name;
    int (*handler) (assuan_context_t, char *line);
  } command_table[] = {
    { "FOO", cmd_foo },
    { "BAR", cmd_bar },
    { "INPUT", NULL },
    { "OUTPUT", NULL },
    { NULL }};

For convenience this table is usually put after the actual command handlers (cmd_foo, cmd_bar) or even put inside command_handler (see below). Note that the commands INPUT and OUTPUT do not require a handler because Libassuan provides a default handler for them. It is however possible to assign a custom handler.

A prerequisite for this example code is that a client has already connected to the server. Often there are two modes combined in one program: A pipe-based server, where a client has forked the server process, or a Unix domain socket based server that is listening on the socket.

void
command_handler (int fd)
{
  gpg_error_t rc;
  int i;
  assuan_context_t ctx;

  rc = assuan_new (&ctx);
  if (rc)
    {
      fprintf (stderr, "server context creation failed: %s\n",
               gpg_strerror(rc));
      return;
    }

  if (fd == -1)
    {
      assuan_fd_t filedes[2];

      filedes[0] = assuan_fd_from_posix_fd (0);
      filedes[1] = assuan_fd_from_posix_fd (1);
      rc = assuan_init_pipe_server (ctx, filedes);
    }
  else
    rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
  if (rc)
    {
      fprintf (stderr, "server init failed: %s\n", gpg_strerror (rc));
      return;
    }

This is the first part of the command handler. We start off by allocating a new Assuan context with assuan_new. See function assuan_new.

In case this is called as a pipe based server, fd will be based as fd and the code assumes that the server’s stdin and stdout file handles are connected to a pipe. The initialization is thus done using the function:

Function: gpg_error_t assuan_init_pipe_server (assuan_context_t ctx, assuan_fd_t filedes[2])

This function takes the two file descriptors from filedes and returns a new Assuan context at r_ctx. As usual, a return value of 0 indicates success and a failure is indicated by returning an error value. In case of error, NULL will be stored at r_ctx.

In case the server has been called using a bi-directional pipe (socketpair), filedes is ignored and the file descriptor is taken from the environment variable _assuan_connection_fd. You generally don’t need to know this, because assuan_pipe_connect, which is called by the client to connect to such a server, automagically sets this variable.

Function: gpg_error_t assuan_init_socket_server (assuan_context_t ctx, assuan_fd_t fd, unsigned int flags)

This function takes the file descriptor fd, which is expected to be associated with a socket, and an Assuan context ctx. The following bits are currently defined for flags:

ASSUAN_SOCKET_SERVER_FDPASSING

If set, sendmsg and recvmesg are used for input and output, which enables the use of descriptor passing.

ASSUAN_SOCKET_SERVER_ACCEPTED

If set, fd refers to an already accepted socket. That is, Libassuan won’t call accept for it. It is suggested to set this bit as it allows better control of the connection state.

As usual, a return value of 0 indicates success and a failure is indicated by returning an error value.

On the Windows platform the following function needs to be called after assuan_init_socket_server:

Function: void assuan_set_sock_nonce ( assuan_context_t ctx, assuan_sock_nonce_t *nonce)

Save a copy of nonce in context ctx. This should be used to register the server’s nonce with a context established by assuan_init_socket_server. It is technically only needed for Windows, but it does no harm to use it on other systems.

After error checking, the implemented assuan commands are registered with the server.

  for (i = 0; command_table[i].name; i++)
    {
      rc = assuan_register_command (ctx,
                                    command_table[i].name,
                                    command_table[i].handler, NULL);
      if (rc)
        {
          fprintf (stderr, "register failed: %s\n", gpg_strerror (rc));
          assuan_release (ctx);
          return;
        }
    }
Data type: gpg_error_t (*assuan_handler_t) (assuan_context_t ctx, char *line)

This is the function invoked by ASSUAN for various command related callback functions. Some of these callback functions have a different type, but most use assuan_handler_t.

Function: gpg_error_t assuan_register_command (assuan_context_t ctx, const char *cmd_string, assuan_handler_t handler, const char *help_string)

This registers the command named cmd_string with the Assuan context ctx. handler is the function called by Libassuan if this command is received from the client. NULL may be used for handler to use a default handler (this only works with a few pre-defined commands). Note that several default handlers have already been registered when the context has been created: NOP, CANCEL, OPTION, BYE, AUTH, RESET and END. It is possible, but not recommended, to override these commands.

help_string is a help string that is used for automatic documentation. It should contain a usage line followed by an empty line and a complete description.

Function: gpg_error_t assuan_register_post_cmd_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t), gpg_error_t err)

Register a function to be called right after a command has been processed. err is the result code from the last internal assuan operation and not the one returned by the handler. It may be used for command-related cleanup.

Function: gpg_error_t assuan_register_bye_notify (assuan_context_t ctx, assuan_handler_t handler)

Register function fnc with context ctx to be called right before the standard handler for the BYE command is being called.

Function: gpg_error_t assuan_register_reset_notify (assuan_context_t ctx, assuan_handler_t handler)

Register function fnc with context ctx to be called right before the standard handler for the RESET command is being called.

Function: gpg_error_t assuan_register_cancel_notify (assuan_context_t ctx, assuan_handler_t handler)

Register function fnc with context ctx to be called right before the standard handler for the RESET command is being called.

Function: gpg_error_t assuan_register_option_handler (assuan_context_t ctx, gpg_error_t (*fnc)(assuan_context_t, const char*, const char*))

Register function fnc with context ctx for processing options. That function is being called with the context, the name and the value of the option. Leading and trailing spaces are removed from the name and the value. The optional leading two dashes of the name are removed as well. If no value has been given, an empty string is passed. The function needs to return 0 on success or an error code.

Function: gpg_error_t assuan_register_input_notify (assuan_context_t ctx, assuan_handler_t handler)

Although the input function may be overridden with a custom handler, it is often more convenient to use the default handler and to know whether an INPUT command has been seen and successfully parsed. The second argument passed to that function is the entire line. Because that line has already been parsed when the function gets called, a file descriptor set with the INPUT command may already be used. That file descriptor is available by calling assuan_get_input_fd. If the notification function returns an error, the input fd does not change.

Function: gpg_error_t assuan_register_output_notify (assuan_context_t ctx, assuan_handler_t handler)

Although the output function may be overridden with a custom handler, it is often more convenient to use the default handler and to know whether an OUTPUT command has been seen and successfully parsed. The second argument passed to that function is the entire line. Because that line has already been parsed when the function gets called, a file descriptor set with the OUTPUT command may already be used. That file descriptor is available by calling assuan_get_output_fd. If the notification function returns an error, the output fd does not change.

Function: gpg_error_t assuan_set_hello_line (assuan_context_t ctx, const char *line)

This is not actually a register function but may be called also after registering commands. It changes the “Hello” line, sent by the server to the client as a first response, from a default string to the string line. For logging purposes, it is often useful to use such a custom hello line which may tell version numbers and such. Linefeeds are allowed in this string, however, each line needs to be shorter than the Assuan line length limit.

Now that everything has been setup, we can start to process our clients requests.

  for (;;)
    {
      rc = assuan_accept (ctx);
      if (rc == -1)
        break;
      else if (rc)
        {
          fprintf (stderr, "accept problem: %s\n", gpg_strerror (rc));
          break;
        }

      rc = assuan_process (ctx);
      if (rc)
        {
          fprintf (stderr, "processing failed: %s\n", gpg_strerror (rc));
          continue;
        }
    }
  assuan_release (ctx);
}

For future extensibility and to properly detect the end of the connection the core of the server should loop over the accept and process calls.

Function: gpg_error_t assuan_accept (assuan_context_t ctx)

A call to this function cancel any existing connection and waits for a connection from a client (that might be skipped, depending on the type of the server). The initial handshake is performed which may include an initial authentication or encryption negotiation. On success 0 is returned. An error value will be returned if the connection could for some reason not be established. An error code of GPG_ERR_EOF indicates the end of the connection.

Function: gpg_error_t assuan_process (assuan_context_t ctx)

This function is used to handle the Assuan protocol after a connection has been established using assuan_accept. It is the main protocol handler responsible for reading the client commands and calling the appropriate handlers. The function returns 0 on success or an error value if something went seriously wrong. Error values from the individual command handlers, i.e. operational error, are not seen here.

That is all needed for the server code. You only need to come up with the code for the individual command handlers. Take care that the line passed to the command handlers is allocated statically within the context and calls to Assuan functions may modify that line. You are also allowed to modify that line which makes parsing much easier.


Next: , Previous: , Up: Top   [Contents][Index]