Next: , Previous: Client code, Up: Top


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 the command_handler. Note that commands with the name “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)
     {
       int i, rc;
       assuan_context_t ctx;
     
       if (fd == -1)
         {
           int filedes[2];
     
           filedes[0] = 0;
           filedes[1] = 1;
           rc = assuan_init_pipe_server (&ctx, filedes);
         }
       else
         rc = assuan_init_socket_server_ext (&ctx, fd, 2);
       if (rc)
         {
           fprintf (stderr, "server init failed: %s\n", gpg_strerror(rc));
           return;
         }

This is the first part of the command handler. 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: assuan_error_t assuan_init_pipe_server (assuan_context_t *r_ctx, int filedes[2])

The 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 a returning an error code. 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 won't need to know that because assuan_pipe_connect_ext, used by the client to connect to such a server, automagically sets this variable.

If a file descriptor has been passed, the assuan context gets initialized by the function:

— Function: assuan_error_t assuan_init_socket_server_ext (assuan_context_t *r_ctx, int fd, unsigned int flags)

The function takes the file descriptor fd which is expected to be associated with a socket and returns a new Assuan context at r_ctx. The following bits are currently defined for flags:

Bit 0
If set, sendmsg and recvmesg are used for input and output and thus enabling the use of descriptor passing.
Bit 1
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 a returning an error code. In case of error, NULL will be stored at r_ctx.

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);
           if (rc)
             {
               fprintf (stderr, "register failed: %s\n", gpg_strerror (rc));
               assuan_deinit_server (ctx);
               return;
             }
         }
— Function: assuan_error_t assuan_register_command (assuan_context_t ctx, const char *cmd_string, int (*handler) (assuan_context_t, char *))

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.

— Function: assuan_error_t assuan_register_post_cmd_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t), int 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: assuan_error_t assuan_register_bye_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t))

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

— Function: assuan_error_t assuan_register_reset_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t))

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

— Function: assuan_error_t assuan_register_cancel_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t))

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

— Function: assuan_error_t assuan_register_option_handler (assuan_context_t ctx, int (*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: assuan_error_t assuan_register_input_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t, const char*))

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.

— Function: assuan_error_t assuan_register_output_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t, const char*))

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.

— Function: assuan_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.

As a last initialization step, debugging may be enabled for the current connection. This is done using

— Function: void assuan_set_log_stream (assuan_context_t ctx, FILE *fp)

Enable debugging for the context ctx and write all debugging output to the stdio stream fp. If the default log stream (used for non-context specific events) has not yet been set, a call to this functions implicitly sets this stream also to fp.

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_deinit_server (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: assuan_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 code will be returned if the connection could for some reason not be established. An error code of -1 indicates the end of the connection.

— Function: assuan_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 code if something went seriously wrong. Error codes from the individual command handlers, i.e. operational error, are not seen here.

After the loop has terminated, the Assuan context needs to be released:

— Function: void assuan_deinit_server (assuan_context_t ctx)

Releases the resources described by the Assuan context ctx It is explicitly allowed to pass NULL for ctx, in which case the function does nothing.

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.