mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-24 10:39:57 +01:00
b008274afd
We better do this once and for all instead of cluttering all future commits with diffs of trailing white spaces. In the majority of cases blank or single lines are affected and thus this change won't disturb a git blame too much. For future commits the pre-commit scripts checks that this won't happen again.
691 lines
16 KiB
C
691 lines
16 KiB
C
/* simple-pwquery.c - A simple password query client for gpg-agent
|
||
* Copyright (C) 2002, 2004, 2007 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of GnuPG.
|
||
*
|
||
* GnuPG 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.
|
||
*
|
||
* GnuPG 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 module is intended as a standalone client implementation to
|
||
gpg-agent's GET_PASSPHRASE command. In particular it does not use
|
||
the Assuan library and can only cope with an already running
|
||
gpg-agent. Some stuff is configurable in the header file. */
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
#include <config.h>
|
||
#endif
|
||
#include <stdlib.h>
|
||
#include <stddef.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <unistd.h>
|
||
#ifdef HAVE_W32_SYSTEM
|
||
#include <winsock2.h>
|
||
#else
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#endif
|
||
#ifdef HAVE_LOCALE_H
|
||
#include <locale.h>
|
||
#endif
|
||
|
||
#define JNLIB_NEED_AFLOCAL
|
||
#include "../common/mischelp.h"
|
||
#ifdef HAVE_W32_SYSTEM
|
||
#include "../common/w32-afunix.h"
|
||
#endif
|
||
|
||
|
||
#define SIMPLE_PWQUERY_IMPLEMENTATION 1
|
||
#include "simple-pwquery.h"
|
||
|
||
#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING)
|
||
# undef SPWQ_USE_LOGGING
|
||
#endif
|
||
|
||
#ifndef _
|
||
#define _(a) (a)
|
||
#endif
|
||
|
||
#if !defined (hexdigitp) && !defined (xtoi_2)
|
||
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
|
||
#define hexdigitp(a) (digitp (a) \
|
||
|| (*(a) >= 'A' && *(a) <= 'F') \
|
||
|| (*(a) >= 'a' && *(a) <= 'f'))
|
||
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
|
||
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
|
||
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
|
||
#endif
|
||
|
||
|
||
/* Name of the socket to be used if GPG_AGENT_INFO has not been
|
||
set. No default socket is used if this is NULL. */
|
||
static char *default_gpg_agent_info;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
#ifndef HAVE_STPCPY
|
||
static char *
|
||
my_stpcpy(char *a,const char *b)
|
||
{
|
||
while( *b )
|
||
*a++ = *b++;
|
||
*a = 0;
|
||
|
||
return (char*)a;
|
||
}
|
||
#define stpcpy(a,b) my_stpcpy((a), (b))
|
||
#endif
|
||
|
||
|
||
|
||
/* Write NBYTES of BUF to file descriptor FD. */
|
||
static int
|
||
writen (int fd, const void *buf, size_t nbytes)
|
||
{
|
||
size_t nleft = nbytes;
|
||
int nwritten;
|
||
|
||
while (nleft > 0)
|
||
{
|
||
#ifdef HAVE_W32_SYSTEM
|
||
nwritten = send (fd, buf, nleft, 0);
|
||
#else
|
||
nwritten = write (fd, buf, nleft);
|
||
#endif
|
||
if (nwritten < 0)
|
||
{
|
||
if (errno == EINTR)
|
||
nwritten = 0;
|
||
else {
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error ("write failed: %s\n", strerror (errno));
|
||
#endif
|
||
return SPWQ_IO_ERROR;
|
||
}
|
||
}
|
||
nleft -= nwritten;
|
||
buf = (const char*)buf + nwritten;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Read an entire line and return number of bytes read. */
|
||
static int
|
||
readline (int fd, char *buf, size_t buflen)
|
||
{
|
||
size_t nleft = buflen;
|
||
char *p;
|
||
int nread = 0;
|
||
|
||
while (nleft > 0)
|
||
{
|
||
#ifdef HAVE_W32_SYSTEM
|
||
int n = recv (fd, buf, nleft, 0);
|
||
#else
|
||
int n = read (fd, buf, nleft);
|
||
#endif
|
||
if (n < 0)
|
||
{
|
||
if (errno == EINTR)
|
||
continue;
|
||
return -(SPWQ_IO_ERROR);
|
||
}
|
||
else if (!n)
|
||
{
|
||
return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
|
||
}
|
||
p = buf;
|
||
nleft -= n;
|
||
buf += n;
|
||
nread += n;
|
||
|
||
for (; n && *p != '\n'; n--, p++)
|
||
;
|
||
if (n)
|
||
{
|
||
break; /* At least one full line available - that's enough.
|
||
This function is just a simple implementation, so
|
||
it is okay to forget about pending bytes. */
|
||
}
|
||
}
|
||
|
||
return nread;
|
||
}
|
||
|
||
|
||
/* Send an option to the agent */
|
||
static int
|
||
agent_send_option (int fd, const char *name, const char *value)
|
||
{
|
||
char buf[200];
|
||
int nread;
|
||
char *line;
|
||
int i;
|
||
|
||
line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
|
||
if (!line)
|
||
return SPWQ_OUT_OF_CORE;
|
||
strcpy (stpcpy (stpcpy (stpcpy (
|
||
stpcpy (line, "OPTION "), name), "="), value), "\n");
|
||
i = writen (fd, line, strlen (line));
|
||
spwq_free (line);
|
||
if (i)
|
||
return i;
|
||
|
||
/* get response */
|
||
nread = readline (fd, buf, DIM(buf)-1);
|
||
if (nread < 0)
|
||
return -nread;
|
||
if (nread < 3)
|
||
return SPWQ_PROTOCOL_ERROR;
|
||
|
||
if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
|
||
return 0; /* okay */
|
||
|
||
return SPWQ_ERR_RESPONSE;
|
||
}
|
||
|
||
|
||
/* Send all available options to the agent. */
|
||
static int
|
||
agent_send_all_options (int fd)
|
||
{
|
||
char *dft_display = NULL;
|
||
char *dft_ttyname = NULL;
|
||
char *dft_ttytype = NULL;
|
||
char *dft_xauthority = NULL;
|
||
char *dft_pinentry_user_data = NULL;
|
||
int rc = 0;
|
||
|
||
dft_display = getenv ("DISPLAY");
|
||
if (dft_display)
|
||
{
|
||
if ((rc = agent_send_option (fd, "display", dft_display)))
|
||
return rc;
|
||
}
|
||
|
||
dft_ttyname = getenv ("GPG_TTY");
|
||
#ifndef HAVE_W32_SYSTEM
|
||
if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
|
||
dft_ttyname = ttyname (0);
|
||
#endif
|
||
if (dft_ttyname && *dft_ttyname)
|
||
{
|
||
if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
|
||
return rc;
|
||
}
|
||
|
||
dft_ttytype = getenv ("TERM");
|
||
if (dft_ttyname && dft_ttytype)
|
||
{
|
||
if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
|
||
return rc;
|
||
}
|
||
|
||
#if defined(HAVE_SETLOCALE)
|
||
{
|
||
char *old_lc = NULL;
|
||
char *dft_lc = NULL;
|
||
|
||
#if defined(LC_CTYPE)
|
||
old_lc = setlocale (LC_CTYPE, NULL);
|
||
if (old_lc)
|
||
{
|
||
char *p = spwq_malloc (strlen (old_lc)+1);
|
||
if (!p)
|
||
return SPWQ_OUT_OF_CORE;
|
||
strcpy (p, old_lc);
|
||
old_lc = p;
|
||
}
|
||
dft_lc = setlocale (LC_CTYPE, "");
|
||
if (dft_ttyname && dft_lc)
|
||
rc = agent_send_option (fd, "lc-ctype", dft_lc);
|
||
if (old_lc)
|
||
{
|
||
setlocale (LC_CTYPE, old_lc);
|
||
spwq_free (old_lc);
|
||
}
|
||
if (rc)
|
||
return rc;
|
||
#endif
|
||
|
||
#if defined(LC_MESSAGES)
|
||
old_lc = setlocale (LC_MESSAGES, NULL);
|
||
if (old_lc)
|
||
{
|
||
char *p = spwq_malloc (strlen (old_lc)+1);
|
||
if (!p)
|
||
return SPWQ_OUT_OF_CORE;
|
||
strcpy (p, old_lc);
|
||
old_lc = p;
|
||
}
|
||
dft_lc = setlocale (LC_MESSAGES, "");
|
||
if (dft_ttyname && dft_lc)
|
||
rc = agent_send_option (fd, "lc-messages", dft_lc);
|
||
if (old_lc)
|
||
{
|
||
setlocale (LC_MESSAGES, old_lc);
|
||
spwq_free (old_lc);
|
||
}
|
||
if (rc)
|
||
return rc;
|
||
#endif
|
||
}
|
||
#endif /*HAVE_SETLOCALE*/
|
||
|
||
/* Send the XAUTHORITY variable. */
|
||
dft_xauthority = getenv ("XAUTHORITY");
|
||
if (dft_xauthority)
|
||
{
|
||
/* We ignore errors here because older gpg-agents don't support
|
||
this option. */
|
||
agent_send_option (fd, "xauthority", dft_xauthority);
|
||
}
|
||
|
||
/* Send the PINENTRY_USER_DATA variable. */
|
||
dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA");
|
||
if (dft_pinentry_user_data)
|
||
{
|
||
/* We ignore errors here because older gpg-agents don't support
|
||
this option. */
|
||
agent_send_option (fd, "pinentry-user-data", dft_pinentry_user_data);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Try to open a connection to the agent, send all options and return
|
||
the file descriptor for the connection. Return -1 in case of
|
||
error. */
|
||
static int
|
||
agent_open (int *rfd)
|
||
{
|
||
int rc;
|
||
int fd;
|
||
char *infostr, *p;
|
||
struct sockaddr_un client_addr;
|
||
size_t len;
|
||
int prot;
|
||
char line[200];
|
||
int nread;
|
||
|
||
*rfd = -1;
|
||
infostr = getenv ( "GPG_AGENT_INFO" );
|
||
if ( !infostr || !*infostr )
|
||
infostr = default_gpg_agent_info;
|
||
if ( !infostr || !*infostr )
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error (_("gpg-agent is not available in this session\n"));
|
||
#endif
|
||
return SPWQ_NO_AGENT;
|
||
}
|
||
p = spwq_malloc (strlen (infostr)+1);
|
||
if (!p)
|
||
return SPWQ_OUT_OF_CORE;
|
||
strcpy (p, infostr);
|
||
infostr = p;
|
||
|
||
if ( !(p = strchr ( infostr, PATHSEP_C)) || p == infostr
|
||
|| (p-infostr)+1 >= sizeof client_addr.sun_path )
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error ( _("malformed GPG_AGENT_INFO environment variable\n"));
|
||
#endif
|
||
return SPWQ_NO_AGENT;
|
||
}
|
||
*p++ = 0;
|
||
|
||
while (*p && *p != PATHSEP_C)
|
||
p++;
|
||
prot = *p? atoi (p+1) : 0;
|
||
if ( prot != 1)
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
|
||
#endif
|
||
return SPWQ_PROTOCOL_ERROR;
|
||
}
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0);
|
||
#else
|
||
fd = socket (AF_UNIX, SOCK_STREAM, 0);
|
||
#endif
|
||
if (fd == -1)
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error ("can't create socket: %s\n", strerror(errno) );
|
||
#endif
|
||
return SPWQ_SYS_ERROR;
|
||
}
|
||
|
||
memset (&client_addr, 0, sizeof client_addr);
|
||
client_addr.sun_family = AF_UNIX;
|
||
strcpy (client_addr.sun_path, infostr);
|
||
len = SUN_LEN (&client_addr);
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
rc = _w32_sock_connect (fd, (struct sockaddr*)&client_addr, len );
|
||
#else
|
||
rc = connect (fd, (struct sockaddr*)&client_addr, len );
|
||
#endif
|
||
if (rc == -1)
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno));
|
||
#endif
|
||
close (fd );
|
||
return SPWQ_IO_ERROR;
|
||
}
|
||
|
||
nread = readline (fd, line, DIM(line));
|
||
if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
|
||
&& (line[2] == '\n' || line[2] == ' ')) )
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error ( _("communication problem with gpg-agent\n"));
|
||
#endif
|
||
close (fd );
|
||
return SPWQ_PROTOCOL_ERROR;
|
||
}
|
||
|
||
rc = agent_send_all_options (fd);
|
||
if (rc)
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error (_("problem setting the gpg-agent options\n"));
|
||
#endif
|
||
close (fd);
|
||
return rc;
|
||
}
|
||
|
||
*rfd = fd;
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Copy text to BUFFER and escape as required. Return a pointer to
|
||
the end of the new buffer. Note that BUFFER must be large enough
|
||
to keep the entire text; allocataing it 3 times the size of TEXT
|
||
is sufficient. */
|
||
static char *
|
||
copy_and_escape (char *buffer, const char *text)
|
||
{
|
||
int i;
|
||
const unsigned char *s = (unsigned char *)text;
|
||
char *p = buffer;
|
||
|
||
|
||
for (i=0; s[i]; i++)
|
||
{
|
||
if (s[i] < ' ' || s[i] == '+')
|
||
{
|
||
sprintf (p, "%%%02X", s[i]);
|
||
p += 3;
|
||
}
|
||
else if (s[i] == ' ')
|
||
*p++ = '+';
|
||
else
|
||
*p++ = s[i];
|
||
}
|
||
return p;
|
||
}
|
||
|
||
|
||
/* Set the name of the default socket to NAME. */
|
||
int
|
||
simple_pw_set_socket (const char *name)
|
||
{
|
||
spwq_free (default_gpg_agent_info);
|
||
if (name)
|
||
{
|
||
default_gpg_agent_info = spwq_malloc (strlen (name) + 4 + 1);
|
||
if (!default_gpg_agent_info)
|
||
return SPWQ_OUT_OF_CORE;
|
||
/* We don't know the PID thus we use 0. */
|
||
strcpy (stpcpy (default_gpg_agent_info, name),
|
||
PATHSEP_S "0" PATHSEP_S "1");
|
||
}
|
||
else
|
||
default_gpg_agent_info = NULL;
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Ask the gpg-agent for a passphrase and present the user with a
|
||
DESCRIPTION, a PROMPT and optionally with a TRYAGAIN extra text.
|
||
If a CACHEID is not NULL it is used to locate the passphrase in in
|
||
the cache and store it under this ID. If OPT_CHECK is true
|
||
gpg-agent is asked to apply some checks on the passphrase security.
|
||
If ERRORCODE is not NULL it should point a variable receiving an
|
||
errorcode; this error code might be 0 if the user canceled the
|
||
operation. The function returns NULL to indicate an error. */
|
||
char *
|
||
simple_pwquery (const char *cacheid,
|
||
const char *tryagain,
|
||
const char *prompt,
|
||
const char *description,
|
||
int opt_check,
|
||
int *errorcode)
|
||
{
|
||
int fd = -1;
|
||
int nread;
|
||
char *result = NULL;
|
||
char *pw = NULL;
|
||
char *p;
|
||
int rc, i;
|
||
|
||
rc = agent_open (&fd);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
if (!cacheid)
|
||
cacheid = "X";
|
||
if (!tryagain)
|
||
tryagain = "X";
|
||
if (!prompt)
|
||
prompt = "X";
|
||
if (!description)
|
||
description = "X";
|
||
|
||
{
|
||
char *line;
|
||
/* We allocate 3 times the needed space so that there is enough
|
||
space for escaping. */
|
||
line = spwq_malloc (15 + 10
|
||
+ 3*strlen (cacheid) + 1
|
||
+ 3*strlen (tryagain) + 1
|
||
+ 3*strlen (prompt) + 1
|
||
+ 3*strlen (description) + 1
|
||
+ 2);
|
||
if (!line)
|
||
{
|
||
rc = SPWQ_OUT_OF_CORE;
|
||
goto leave;
|
||
}
|
||
strcpy (line, "GET_PASSPHRASE ");
|
||
p = line+15;
|
||
if (opt_check)
|
||
p = stpcpy (p, "--check ");
|
||
p = copy_and_escape (p, cacheid);
|
||
*p++ = ' ';
|
||
p = copy_and_escape (p, tryagain);
|
||
*p++ = ' ';
|
||
p = copy_and_escape (p, prompt);
|
||
*p++ = ' ';
|
||
p = copy_and_escape (p, description);
|
||
*p++ = '\n';
|
||
rc = writen (fd, line, p - line);
|
||
spwq_free (line);
|
||
if (rc)
|
||
goto leave;
|
||
}
|
||
|
||
/* get response */
|
||
pw = spwq_secure_malloc (500);
|
||
nread = readline (fd, pw, 499);
|
||
if (nread < 0)
|
||
{
|
||
rc = -nread;
|
||
goto leave;
|
||
}
|
||
if (nread < 3)
|
||
{
|
||
rc = SPWQ_PROTOCOL_ERROR;
|
||
goto leave;
|
||
}
|
||
|
||
if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ')
|
||
{ /* we got a passphrase - convert it back from hex */
|
||
size_t pwlen = 0;
|
||
|
||
for (i=3; i < nread && hexdigitp (pw+i); i+=2)
|
||
pw[pwlen++] = xtoi_2 (pw+i);
|
||
pw[pwlen] = 0; /* make a C String */
|
||
result = pw;
|
||
pw = NULL;
|
||
}
|
||
else if ((nread > 7 && !memcmp (pw, "ERR 111", 7)
|
||
&& (pw[7] == ' ' || pw[7] == '\n') )
|
||
|| ((nread > 4 && !memcmp (pw, "ERR ", 4)
|
||
&& (strtoul (pw+4, NULL, 0) & 0xffff) == 99)) )
|
||
{
|
||
/* 111 is the old Assuan code for canceled which might still
|
||
be in use by old installations. 99 is GPG_ERR_CANCELED as
|
||
used by modern gpg-agents; 0xffff is used to mask out the
|
||
error source. */
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_info (_("canceled by user\n") );
|
||
#endif
|
||
*errorcode = 0; /* Special error code to indicate Cancel. */
|
||
}
|
||
else if (nread > 4 && !memcmp (pw, "ERR ", 4))
|
||
{
|
||
switch ( (strtoul (pw+4, NULL, 0) & 0xffff) )
|
||
{
|
||
case 85: rc = SPWQ_NO_PIN_ENTRY; break;
|
||
default: rc = SPWQ_GENERAL_ERROR; break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error (_("problem with the agent\n"));
|
||
#endif
|
||
rc = SPWQ_ERR_RESPONSE;
|
||
}
|
||
|
||
leave:
|
||
if (errorcode)
|
||
*errorcode = rc;
|
||
if (fd != -1)
|
||
close (fd);
|
||
if (pw)
|
||
spwq_secure_free (pw);
|
||
return result;
|
||
}
|
||
|
||
|
||
/* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */
|
||
int
|
||
simple_pwclear (const char *cacheid)
|
||
{
|
||
char line[500];
|
||
char *p;
|
||
|
||
/* We need not more than 50 characters for the command and the
|
||
terminating nul. */
|
||
if (strlen (cacheid) * 3 > sizeof (line) - 50)
|
||
return SPWQ_PROTOCOL_ERROR;
|
||
|
||
strcpy (line, "CLEAR_PASSPHRASE ");
|
||
p = line + 17;
|
||
p = copy_and_escape (p, cacheid);
|
||
*p++ = '\n';
|
||
*p++ = '\0';
|
||
|
||
return simple_query (line);
|
||
}
|
||
|
||
|
||
/* Perform the simple query QUERY (which must be new-line and 0
|
||
terminated) and return the error code. */
|
||
int
|
||
simple_query (const char *query)
|
||
{
|
||
int fd = -1;
|
||
int nread;
|
||
char response[500];
|
||
int rc;
|
||
|
||
rc = agent_open (&fd);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
rc = writen (fd, query, strlen (query));
|
||
if (rc)
|
||
goto leave;
|
||
|
||
/* get response */
|
||
nread = readline (fd, response, 499);
|
||
if (nread < 0)
|
||
{
|
||
rc = -nread;
|
||
goto leave;
|
||
}
|
||
if (nread < 3)
|
||
{
|
||
rc = SPWQ_PROTOCOL_ERROR;
|
||
goto leave;
|
||
}
|
||
|
||
if (response[0] == 'O' && response[1] == 'K')
|
||
/* OK, do nothing. */;
|
||
else if ((nread > 7 && !memcmp (response, "ERR 111", 7)
|
||
&& (response[7] == ' ' || response[7] == '\n') )
|
||
|| ((nread > 4 && !memcmp (response, "ERR ", 4)
|
||
&& (strtoul (response+4, NULL, 0) & 0xffff) == 99)) )
|
||
{
|
||
/* 111 is the old Assuan code for canceled which might still
|
||
be in use by old installations. 99 is GPG_ERR_CANCELED as
|
||
used by modern gpg-agents; 0xffff is used to mask out the
|
||
error source. */
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_info (_("canceled by user\n") );
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
#ifdef SPWQ_USE_LOGGING
|
||
log_error (_("problem with the agent\n"));
|
||
#endif
|
||
rc = SPWQ_ERR_RESPONSE;
|
||
}
|
||
|
||
leave:
|
||
if (fd != -1)
|
||
close (fd);
|
||
return rc;
|
||
}
|