Esempio n. 1
0
/* Set the text used for the next OK reponse.  This string is
   automatically reset to NULL after the next command. */
gpg_error_t
assuan_set_okay_line (assuan_context_t ctx, const char *line)
{
  if (!ctx)
    return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
  if (!line)
    {
      _assuan_free (ctx, ctx->okay_line);
      ctx->okay_line = NULL;
    }
  else
    {
      /* FIXME: we need to use gcry_is_secure() to test whether
         we should allocate the entire line in secure memory */
      char *buf = _assuan_malloc (ctx, 3 + strlen(line) + 1);
      if (!buf)
        return _assuan_error (ctx, gpg_err_code_from_syserror ());
      strcpy (buf, "OK ");
      strcpy (buf+3, line);
      _assuan_free (ctx, ctx->okay_line);
      ctx->okay_line = buf;
    }
  return 0;
}
Esempio n. 2
0
static void *
get_membuf (assuan_context_t ctx, struct membuf *mb, size_t *len)
{
  char *p;

  if (mb->out_of_core || mb->too_large)
    {
      _assuan_free (ctx, mb->buf);
      mb->buf = NULL;
      return NULL;
    }

  mb->buf[mb->len] = 0; /* there is enough space for the hidden eos */
  p = mb->buf;
  *len = mb->len;
  mb->buf = NULL;
  mb->out_of_core = 1; /* don't allow a reuse */
  return p;
}
Esempio n. 3
0
static gpg_error_t
_assuan_connect_finalize (assuan_context_t ctx, assuan_fd_t fd,
                          unsigned int flags)
{
  gpg_error_t err;

  ctx->engine.release = _assuan_client_release;
  ctx->engine.readfnc = _assuan_simple_read;
  ctx->engine.writefnc = _assuan_simple_write;
  ctx->engine.sendfd = NULL;
  ctx->engine.receivefd = NULL;
  ctx->finish_handler = _assuan_client_finish;
  ctx->inbound.fd = fd;
  ctx->outbound.fd = fd;
  ctx->max_accepts = -1;

  if (flags & ASSUAN_SOCKET_CONNECT_FDPASSING)
    _assuan_init_uds_io (ctx);

  /* initial handshake */
  {
    assuan_response_t response;
    int off;

    err = _assuan_read_from_server (ctx, &response, &off, 0);
    if (err)
      TRACE1 (ctx, ASSUAN_LOG_SYSIO, "assuan_socket_connect", ctx,
	      "can't connect to server: %s\n", gpg_strerror (err));
    else if (response != ASSUAN_RESPONSE_OK)
      {
	char *sname = _assuan_encode_c_string (ctx, ctx->inbound.line);
	if (sname)
	  {
	    TRACE1 (ctx, ASSUAN_LOG_SYSIO, "assuan_socket_connect", ctx,
		    "can't connect to server: %s", sname);
	    _assuan_free (ctx, sname);
	  }
	err = _assuan_error (ctx, GPG_ERR_ASS_CONNECT_FAILED);
      }
  }

  return err;
}
Esempio n. 4
0
gpg_error_t
assuan_write_status (assuan_context_t ctx,
                     const char *keyword, const char *text)
{
  char buffer[256];
  char *helpbuf;
  size_t n;
  gpg_error_t ae;

  if ( !ctx || !keyword)
    return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
  if (!text)
    text = "";

  n = 2 + strlen (keyword) + 1 + strlen (text) + 1;
  if (n < sizeof (buffer))
    {
      strcpy (buffer, "S ");
      strcat (buffer, keyword);
      if (*text)
        {
          strcat (buffer, " ");
          strcat (buffer, text);
        }
      ae = assuan_write_line (ctx, buffer);
    }
  else if ( (helpbuf = _assuan_malloc (ctx, n)) )
    {
      strcpy (helpbuf, "S ");
      strcat (helpbuf, keyword);
      if (*text)
        {
          strcat (helpbuf, " ");
          strcat (helpbuf, text);
        }
      ae = assuan_write_line (ctx, helpbuf);
      _assuan_free (ctx, helpbuf);
    }
  else
    ae = 0;
  return ae;
}
Esempio n. 5
0
static void
free_membuf (assuan_context_t ctx, struct membuf *mb)
{
  _assuan_free (ctx, mb->buf);
  mb->buf = NULL;
}
Esempio n. 6
0
int
__assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name,
                const char **argv,
                assuan_fd_t fd_in, assuan_fd_t fd_out,
                assuan_fd_t *fd_child_list,
                void (*atfork) (void *opaque, int reserved),
                void *atforkvalue, unsigned int flags)
{
    SECURITY_ATTRIBUTES sec_attr;
    PROCESS_INFORMATION pi =
    {
        NULL,      /* Returns process handle.  */
        0,         /* Returns primary thread handle.  */
        0,         /* Returns pid.  */
        0          /* Returns tid.  */
    };
    STARTUPINFO si;
    assuan_fd_t fd;
    assuan_fd_t *fdp;
    char *cmdline;
    HANDLE nullfd = INVALID_HANDLE_VALUE;

    /* fixme: Actually we should set the "_assuan_pipe_connect_pid" env
       variable.  However this requires us to write a full environment
       handler, because the strings are expected in sorted order.  The
       suggestion given in the MS Reference Library, to save the old
       value, change it, create process and restore it, is not thread
       safe.  */

    /* Build the command line.  */
    if (build_w32_commandline (ctx, argv, &cmdline))
        return -1;

    /* Start the process.  */
    memset (&sec_attr, 0, sizeof sec_attr);
    sec_attr.nLength = sizeof sec_attr;
    sec_attr.bInheritHandle = FALSE;

    memset (&si, 0, sizeof si);
    si.cb = sizeof (si);
    si.dwFlags = STARTF_USESTDHANDLES;
    /* FIXME: Dup to nul if ASSUAN_INVALID_FD.  */
    si.hStdInput  = fd_in;
    si.hStdOutput = fd_out;

    /* Dup stderr to /dev/null unless it is in the list of FDs to be
       passed to the child. */
    fd = assuan_fd_from_posix_fd (fileno (stderr));
    fdp = fd_child_list;
    if (fdp)
    {
        for (; *fdp != ASSUAN_INVALID_FD && *fdp != fd; fdp++)
            ;
    }
    if (!fdp || *fdp == ASSUAN_INVALID_FD)
    {
        nullfd = CreateFileW (L"nul", GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              NULL, OPEN_EXISTING, 0, NULL);
        if (nullfd == INVALID_HANDLE_VALUE)
        {
            TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx,
                    "can't open `nul': %s", _assuan_w32_strerror (ctx, -1));
            _assuan_free (ctx, cmdline);
            gpg_err_set_errno (EIO);
            return -1;
        }
        si.hStdError = nullfd;
    }
    else
        si.hStdError = fd;

    /* Note: We inherit all handles flagged as inheritable.  This seems
       to be a security flaw but there seems to be no way of selecting
       handles to inherit.  A fix for this would be to use a helper
       process like we have in gpgme.  */
    /*   _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */
    /*                       name, cmdline); */
    if (!CreateProcess (name,                 /* Program to start.  */
                        cmdline,              /* Command line arguments.  */
                        &sec_attr,            /* Process security attributes.  */
                        &sec_attr,            /* Thread security attributes.  */
                        TRUE,                 /* Inherit handles.  */
                        (CREATE_DEFAULT_ERROR_MODE
                         | ((flags & 128)? DETACHED_PROCESS : 0)
                         | GetPriorityClass (GetCurrentProcess ())
                         | CREATE_SUSPENDED), /* Creation flags.  */
                        NULL,                 /* Environment.  */
                        NULL,                 /* Use current drive/directory.  */
                        &si,                  /* Startup information. */
                        &pi                   /* Returns process information.  */
                       ))
    {
        TRACE1 (ctx, ASSUAN_LOG_SYSIO, "pipe_connect_w32", ctx,
                "CreateProcess failed: %s", _assuan_w32_strerror (ctx, -1));
        _assuan_free (ctx, cmdline);
        if (nullfd != INVALID_HANDLE_VALUE)
            CloseHandle (nullfd);

        gpg_err_set_errno (EIO);
        return -1;
    }

    _assuan_free (ctx, cmdline);
    if (nullfd != INVALID_HANDLE_VALUE)
        CloseHandle (nullfd);

    ResumeThread (pi.hThread);
    CloseHandle (pi.hThread);

    /*   _assuan_log_printf ("CreateProcess ready: hProcess=%p hThread=%p" */
    /*                       " dwProcessID=%d dwThreadId=%d\n", */
    /*                       pi.hProcess, pi.hThread, */
    /*                       (int) pi.dwProcessId, (int) pi.dwThreadId); */

    *r_pid = (pid_t) pi.hProcess;

    /* No need to modify peer process, as we don't change the handle
       names.  However this also means we are not safe, as we inherit
       too many handles.  Should use approach similar to gpgme and glib
       using a helper process.  */

    return 0;
}
Esempio n. 7
0
/* Make a connection to the Unix domain socket NAME and return a new
   Assuan context in CTX.  SERVER_PID is currently not used but may
   become handy in the future.  Defined flag bits are:

     ASSUAN_SOCKET_CONNECT_FDPASSING
        sendmsg and recvmsg are used.

   NAME must either start with a slash and optional with a drive
   prefix ("c:") or use one of these URL schemata:

      file://<fname>

        This is the same as the default just with an explicit schemata.

      assuan://<ipaddr>:<port>
      assuan://[<ip6addr>]:<port>

        Connect using TCP to PORT of the server with the numerical
        IPADDR.  Note that '[' and ']' are literal characters.

  */
gpg_error_t
assuan_socket_connect (assuan_context_t ctx, const char *name,
		       pid_t server_pid, unsigned int flags)
{
  gpg_error_t err = 0;
  assuan_fd_t fd;
#ifdef WITH_IPV6
  struct sockaddr_in6 srvr_addr_in6;
#endif
  struct sockaddr_un srvr_addr_un;
  struct sockaddr_in srvr_addr_in;
  struct sockaddr *srvr_addr = NULL;
  uint16_t port = 0;
  size_t len = 0;
  const char *s;
  int af = AF_LOCAL;
  int pf = PF_LOCAL;

  TRACE2 (ctx, ASSUAN_LOG_CTX, "assuan_socket_connect", ctx,
	  "name=%s, flags=0x%x", name ? name : "(null)", flags);

  if (!ctx || !name)
    return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);

  if (!strncmp (name, "file://", 7) && name[7])
    name += 7;
  else if (!strncmp (name, "assuan://", 9) && name[9])
    {
      name += 9;
      af = AF_INET;
      pf = PF_INET;
    }
  else /* Default.  */
    {
      /* We require that the name starts with a slash if no URL
         schemata is used.  To make things easier we allow an optional
         drive prefix.  */
      s = name;
      if (*s && s[1] == ':')
        s += 2;
      if (*s != DIRSEP_C && *s != '/')
        return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);
    }

  if (af == AF_LOCAL)
    {
      if (strlen (name)+1 >= sizeof srvr_addr_un.sun_path)
        return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);

      memset (&srvr_addr_un, 0, sizeof srvr_addr_un);
      srvr_addr_un.sun_family = AF_LOCAL;
      strncpy (srvr_addr_un.sun_path, name, sizeof (srvr_addr_un.sun_path) - 1);
      srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path) - 1] = 0;
      len = SUN_LEN (&srvr_addr_un);

      srvr_addr = (struct sockaddr *)&srvr_addr_un;
    }
  else
    {
      char *addrstr, *p;
#ifdef HAVE_INET_PTON
      void *addrbuf = NULL;
#endif

      addrstr = _assuan_malloc (ctx, strlen (name) + 1);
      if (!addrstr)
        return _assuan_error (ctx, gpg_err_code_from_syserror ());

      if (*name == '[')
        {
          strcpy (addrstr, name+1);
          p = strchr (addrstr, ']');
          if (!p || p[1] != ':' || !parse_portno (p+2, &port))
            err = _assuan_error (ctx, GPG_ERR_BAD_URI);
          else
            {
              *p = 0;
#ifdef WITH_IPV6
              af = AF_INET6;
              pf = PF_INET6;
              memset (&srvr_addr_in6, 0, sizeof srvr_addr_in6);
              srvr_addr_in6.sin6_family = af;
              srvr_addr_in6.sin6_port = htons (port);
#ifdef HAVE_INET_PTON
              addrbuf = &srvr_addr_in6.sin6_addr;
#endif
              srvr_addr = (struct sockaddr *)&srvr_addr_in6;
              len = sizeof srvr_addr_in6;
#else
              err =  _assuan_error (ctx, GPG_ERR_EAFNOSUPPORT);
#endif
            }
        }
      else
        {
          strcpy (addrstr, name);
          p = strchr (addrstr, ':');
          if (!p || !parse_portno (p+1, &port))
            err = _assuan_error (ctx, GPG_ERR_BAD_URI);
          else
            {
              *p = 0;
              memset (&srvr_addr_in, 0, sizeof srvr_addr_in);
              srvr_addr_in.sin_family = af;
              srvr_addr_in.sin_port = htons (port);
#ifdef HAVE_INET_PTON
              addrbuf = &srvr_addr_in.sin_addr;
#endif
              srvr_addr = (struct sockaddr *)&srvr_addr_in;
              len = sizeof srvr_addr_in;
            }
        }

      if (!err)
        {
#ifdef HAVE_INET_PTON
          switch (inet_pton (af, addrstr, addrbuf))
            {
            case 1:  break;
            case 0:  err = _assuan_error (ctx, GPG_ERR_BAD_URI); break;
            default: err = _assuan_error (ctx, gpg_err_code_from_syserror ());
            }
#else /*!HAVE_INET_PTON*/
          /* We need to use the old function.  If we are here v6
             support isn't enabled anyway and thus we can do fine
             without.  Note that Windows as a compatible inet_pton
             function named inetPton, but only since Vista.  */
          srvr_addr_in.sin_addr.s_addr = inet_addr (addrstr);
          if (srvr_addr_in.sin_addr.s_addr == INADDR_NONE)
            err = _assuan_error (ctx, GPG_ERR_BAD_URI);
#endif /*!HAVE_INET_PTON*/
        }

      _assuan_free (ctx, addrstr);
      if (err)
        return err;
    }

  fd = _assuan_sock_new (ctx, pf, SOCK_STREAM, 0);
  if (fd == ASSUAN_INVALID_FD)
    {
      err = _assuan_error (ctx, gpg_err_code_from_syserror ());
      TRACE1 (ctx, ASSUAN_LOG_SYSIO, "assuan_socket_connect", ctx,
              "can't create socket: %s", strerror (errno));
      return err;
    }

  if (_assuan_sock_connect (ctx, fd, srvr_addr, len) == -1)
    {
      TRACE2 (ctx, ASSUAN_LOG_SYSIO, "assuan_socket_connect", ctx,
	      "can't connect to `%s': %s\n", name, strerror (errno));
      _assuan_close (ctx, fd);
      return _assuan_error (ctx, GPG_ERR_ASS_CONNECT_FAILED);
    }

  err = _assuan_connect_finalize (ctx, fd, flags);

  if (err)
    _assuan_reset (ctx);

  return err;
}
Esempio n. 8
0
/* This function is similar to pipe_connect but uses a socketpair and
   sets the I/O up to use sendmsg/recvmsg. */
static gpg_error_t
socketpair_connect (assuan_context_t ctx,
                    const char *name, const char **argv,
                    assuan_fd_t *fd_child_list,
                    void (*atfork) (void *opaque, int reserved),
                    void *atforkvalue)
{
  gpg_error_t err;
  int idx;
  int fds[2];
  char mypidstr[50];
  pid_t pid;
  int *child_fds = NULL;
  int child_fds_cnt = 0;
  struct at_socketpair_fork atp;
  int rc;

  TRACE_BEG3 (ctx, ASSUAN_LOG_CTX, "socketpair_connect", ctx,
	      "name=%s,atfork=%p,atforkvalue=%p", name ? name : "(null)",
	      atfork, atforkvalue);

  atp.user_atfork = atfork;
  atp.user_atforkvalue = atforkvalue;
  atp.parent_pid = getpid ();

  if (!ctx
      || (name && (!argv || !argv[0]))
      || (!name && !argv))
    return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE);

  if (! ctx->flags.no_fixsignals)
    fix_signals ();

  sprintf (mypidstr, "%lu", (unsigned long)getpid ());

  if (fd_child_list)
    while (fd_child_list[child_fds_cnt] != ASSUAN_INVALID_FD)
      child_fds_cnt++;
  child_fds = _assuan_malloc (ctx, (child_fds_cnt + 2) * sizeof (int));
  if (! child_fds)
    return TRACE_ERR (gpg_err_code_from_syserror ());
  child_fds[1] = ASSUAN_INVALID_FD;
  if (fd_child_list)
    memcpy (&child_fds[1], fd_child_list, (child_fds_cnt + 1) * sizeof (int));

  if (_assuan_socketpair (ctx, AF_LOCAL, SOCK_STREAM, 0, fds))
    {
      TRACE_LOG1 ("socketpair failed: %s", strerror (errno));
      _assuan_free (ctx, child_fds);
      return TRACE_ERR (GPG_ERR_ASS_GENERAL);
    }
  atp.peer_fd = fds[1];
  child_fds[0] = fds[1];

  rc = _assuan_spawn (ctx, &pid, name, argv, ASSUAN_INVALID_FD,
		      ASSUAN_INVALID_FD, child_fds, at_socketpair_fork_cb,
		      &atp, 0);
  if (rc < 0)
    {
      err = gpg_err_code_from_syserror ();
      _assuan_close (ctx, fds[0]);
      _assuan_close (ctx, fds[1]);
      _assuan_free (ctx, child_fds);
      return TRACE_ERR (err);
    }

  /* For W32, the user needs to know the server-local names of the
     inherited handles.  Return them here.  Note that the translation
     of the peer socketpair fd (fd_child_list[0]) must be done by the
     wrapper program based on the environment variable
     _assuan_connection_fd.  */
  if (fd_child_list)
    {
      for (idx = 0; fd_child_list[idx] != -1; idx++)
	/* We add 1 to skip over the socketpair end.  */
	fd_child_list[idx] = child_fds[idx + 1];
    }

  _assuan_free (ctx, child_fds);

  /* If this is the server child process, exit early.  */
  if (! name && (*argv)[0] == 's')
    {
      _assuan_close (ctx, fds[0]);
      return 0;
    }

  _assuan_close (ctx, fds[1]);

  ctx->engine.release = _assuan_client_release;
  ctx->finish_handler = _assuan_client_finish;
  ctx->max_accepts = 1;
  ctx->inbound.fd  = fds[0];
  ctx->outbound.fd = fds[0];
  _assuan_init_uds_io (ctx);

  err = initial_handshake (ctx);
  if (err)
    _assuan_reset (ctx);
  return err;
}
Esempio n. 9
0
int
__assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name,
		const char **argv,
		assuan_fd_t fd_in, assuan_fd_t fd_out,
		assuan_fd_t *fd_child_list,
		void (*atfork) (void *opaque, int reserved),
		void *atforkvalue, unsigned int flags)
{
  PROCESS_INFORMATION pi = 
    {
      NULL,      /* Returns process handle.  */
      0,         /* Returns primary thread handle.  */
      0,         /* Returns pid.  */
      0          /* Returns tid.  */
    };
  assuan_fd_t fd;
  assuan_fd_t *fdp;
  assuan_fd_t fd_err;
  int fd_err_isnull = 0;
  char *cmdline;

  /* Dup stderr to /dev/null unless it is in the list of FDs to be
     passed to the child.  Well we don't actually open nul because
     that is not available on Windows, but use our hack for it.
     Because an RVID of 0 is an invalid value and HANDLES will never
     have this value either, we test for this as well.  */

  /* FIXME: As long as we can't decide whether a handle is a real
     handler or an rendezvous id we can't do anything with the
     FD_CHILD_LIST.  We can't do much with stderr either, thus we
     better don't pass stderr to the child at all.  If we would do so
     and it is not a rendezvous id the client would run into
     problems.  */
  fd = assuan_fd_from_posix_fd (fileno (stderr));
  fdp = fd_child_list;
  if (fdp)
    {
      for (; *fdp != ASSUAN_INVALID_FD && *fdp != 0 && *fdp != fd; fdp++)
        ;
    }
  if (!fdp || *fdp == ASSUAN_INVALID_FD)
    fd_err_isnull = 1;
  fd_err = ASSUAN_INVALID_FD;

  if (build_w32_commandline (ctx, argv, fd_in, fd_out, fd_err, fd_err_isnull,
                             &cmdline))
    {
      return -1;
    }

  TRACE2 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx,
          "path=`%s' cmdline=`%s'", name, cmdline);

  {
    wchar_t *wcmdline, *wname;

    wcmdline = utf8_to_wchar (cmdline);
    _assuan_free (ctx, cmdline);
    if (!wcmdline)
      return -1;

    wname = utf8_to_wchar (name);
    if (!wname)
      {
        free_wchar (wcmdline);
        return -1;
      }
    
    if (!CreateProcess (wname,                /* Program to start.  */
                        wcmdline,             /* Command line arguments.  */
                        NULL,                 /* (not supported)  */
                        NULL,                 /* (not supported)  */
                        FALSE,                /* (not supported)  */
                        (CREATE_SUSPENDED),   /* Creation flags.  */
                        NULL,                 /* (not supported)  */
                        NULL,                 /* (not supported)  */
                        NULL,                 /* (not supported) */
                        &pi                   /* Returns process information.*/
                        ))
      {
        TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx,
                "CreateProcess failed: %s", _assuan_w32_strerror (ctx, -1));
        free_wchar (wname);
        free_wchar (wcmdline);
        gpg_err_set_errno (EIO);
        return -1;
      }
    free_wchar (wname);
    free_wchar (wcmdline);
  }

  ResumeThread (pi.hThread);

  TRACE4 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx,
          "CreateProcess ready: hProcess=%p hThread=%p"
          " dwProcessID=%d dwThreadId=%d\n",
          pi.hProcess, pi.hThread, (int) pi.dwProcessId, (int) pi.dwThreadId);

  CloseHandle (pi.hThread); 
  
  *r_pid = (pid_t) pi.hProcess;
  return 0;
}
Esempio n. 10
0
/* Call this to acknowledge the current command.  */
gpg_error_t
assuan_process_done (assuan_context_t ctx, gpg_error_t rc)
{
  if (!ctx->in_command)
    return _assuan_error (ctx, GPG_ERR_ASS_GENERAL);

  ctx->in_command = 0;

  /* Check for data write errors.  */
  if (ctx->outbound.data.fp)
    {
      /* Flush the data lines.  */
      fclose (ctx->outbound.data.fp);
      ctx->outbound.data.fp = NULL;
      if (!rc && ctx->outbound.data.error)
	rc = ctx->outbound.data.error;
    }
  else
    {
      /* Flush any data send without using the data FP.  */
      assuan_send_data (ctx, NULL, 0);
      if (!rc && ctx->outbound.data.error)
	rc = ctx->outbound.data.error;
    }

  /* Error handling.  */
  if (!rc)
    {
      if (ctx->process_complete)
	{
	  /* No error checking because the peer may have already
	     disconnect. */
	  assuan_write_line (ctx, "OK closing connection");
	  ctx->finish_handler (ctx);
	}
      else
	rc = assuan_write_line (ctx, ctx->okay_line ? ctx->okay_line : "OK");
    }
  else
    {
      char errline[300];
      const char *text = ctx->err_no == rc ? ctx->err_str : NULL;
      char ebuf[50];

      gpg_strerror_r (rc, ebuf, sizeof (ebuf));
      sprintf (errline, "ERR %d %.50s <%.30s>%s%.100s",
	       rc, ebuf, gpg_strsource (rc),
	       text? " - ":"", text?text:"");

      rc = assuan_write_line (ctx, errline);
    }

  if (ctx->post_cmd_notify_fnc)
    ctx->post_cmd_notify_fnc (ctx, rc);

  ctx->flags.confidential = 0;
  if (ctx->okay_line)
    {
      _assuan_free (ctx, ctx->okay_line);
      ctx->okay_line = NULL;
    }

  return rc;
}