mirror of
git://git.gnupg.org/gnupg.git
synced 2025-07-02 22:46:30 +02:00
This commit was manufactured by cvs2svn to create branch
'GNUPG-1-9-BRANCH'.
This commit is contained in:
parent
8d76177f10
commit
82a17c9fb3
563 changed files with 0 additions and 267875 deletions
139
common/ChangeLog
139
common/ChangeLog
|
@ -1,139 +0,0 @@
|
|||
2002-09-04 Neal H. Walfield <neal@g10code.de>
|
||||
|
||||
* vasprintf.c (vasprintf) [va_copy]: Use va_copy.
|
||||
[!va_copy && __va_copy]: Use __va_copy.
|
||||
[!va_copy && !__va_copy]: Only now fall back to using memcpy.
|
||||
|
||||
2002-08-21 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* errors.h: Added STATUS_IMPORT_PROBLEM.
|
||||
|
||||
2002-08-20 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* vasprintf.c: Hack to handle NULL for %s.
|
||||
|
||||
2002-08-09 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* signal.c: New. Taken from GnuPG 1.1.91.
|
||||
|
||||
2002-07-23 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* util.h (_IO_cookie_io_functions_t): Fixed typo. Noted by
|
||||
Richard Lefebvre.
|
||||
|
||||
2002-07-22 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* fseeko.c, ftello.c: New.
|
||||
|
||||
2002-06-28 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_to_assuan_status): Map more errorcodes to Bad
|
||||
Certificate.
|
||||
|
||||
2002-06-26 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_to_assuan_status): Map EOF to No_Data_Available.
|
||||
|
||||
2002-06-10 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* errors.h (gnupg_error_token): Add new prototype.
|
||||
(STATUS_ERROR): New.
|
||||
|
||||
* mkerrtok: New.
|
||||
* Makefile.am: Use it to create the new error token function.
|
||||
|
||||
2002-06-04 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_to_assuan_status): Map Bad_CA_Certificate.
|
||||
|
||||
2002-05-23 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* no-pth.c, Makefile.am: Removed.
|
||||
|
||||
2002-05-22 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* mkdtemp.c: Replaced byte by unsigned char because it is no longer
|
||||
defined in gcrypt.h.
|
||||
|
||||
2002-05-21 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_gcry_err): Add libgcrypt's new S-expression errors.
|
||||
(map_ksba_err): Add a few mappings.
|
||||
|
||||
2002-05-14 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* gettime.c: New.
|
||||
|
||||
2002-05-03 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* errors.h: Added STARUS_EXPSIG and STATUS_EXPKEYSIG.
|
||||
|
||||
2002-04-15 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* cryptmiss.c: New.
|
||||
|
||||
2002-02-14 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c: Add more assuan<->gnupg mappings.
|
||||
|
||||
2002-02-12 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* fopencookie.c: Dummy function.
|
||||
|
||||
* vasprintf.c: New. Taken from binutils-2.9.1 and dropped all non
|
||||
ANSI-C stuff. Merged with asprintf version.
|
||||
|
||||
* no-pth.c: New.
|
||||
|
||||
2002-01-23 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* mkdtemp.c: Copied from gnupg-1.0.6c and changed to use libgcrypt.
|
||||
|
||||
2002-01-19 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* sysutils.c: New. This is the misc.c file from gnupg 1.0.6 with
|
||||
the OpenPGP stuff removed.
|
||||
* sysutils.h: New.
|
||||
|
||||
2002-01-15 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c: Add mapping for Not_Trusted.
|
||||
|
||||
2002-01-11 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_assuan_err): Codes for CRL
|
||||
|
||||
2002-01-08 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* util.h (spacep): New.
|
||||
|
||||
2002-01-02 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_to_assuan_status): New. Merged from ../agent
|
||||
and ../sm.
|
||||
|
||||
2001-12-20 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* maperror.c (map_gcry_err): Add some mappings.
|
||||
|
||||
2001-12-18 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* Makefile.am (AM_CPPFLAGS): Include flags for gcrypt and ksba
|
||||
|
||||
2001-12-14 Werner Koch <wk@gnupg.org>
|
||||
|
||||
* util.h (digitp, hexdigitp): New ctype like macros.
|
||||
(atoi_1,atoi_2,atoi_4,xtoi_1,xtoi_2): New.
|
||||
|
||||
|
||||
Copyright 2001, 2002 Free Software Foundation, Inc.
|
||||
|
||||
This file is free software; as a special exception the author gives
|
||||
unlimited permission to copy and/or distribute it, with or without
|
||||
modifications, as long as this notice is preserved.
|
||||
|
||||
This file is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
|
||||
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# Makefile for common gnupg modules
|
||||
# Copyright (C) 2001 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 2 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, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
EXTRA_DIST = mkerrors mkerrtok
|
||||
#INCLUDES =
|
||||
BUILT_SOURCES = errors.c
|
||||
|
||||
noinst_LIBRARIES = libcommon.a
|
||||
|
||||
AM_CPPFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
|
||||
|
||||
libcommon_a_SOURCES = \
|
||||
util.h i18n.h \
|
||||
errors.c errors.h \
|
||||
maperror.c \
|
||||
sysutils.c sysutils.h \
|
||||
cryptmiss.c \
|
||||
gettime.c \
|
||||
signal.c
|
||||
|
||||
|
||||
libcommon_a_LIBADD = @LIBOBJS@
|
||||
|
||||
errors.c : errors.h mkerrors mkerrtok
|
||||
$(srcdir)/mkerrors < $(srcdir)/errors.h > errors.c
|
||||
$(srcdir)/mkerrtok < $(srcdir)/errors.h >> errors.c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
Stuff used by several modules of GnuPG. This way we can share error
|
||||
codes and serveral other things.
|
||||
|
||||
|
||||
These directories use it:
|
||||
|
||||
gpg
|
||||
sm
|
||||
agent
|
||||
|
||||
These directories don't use it:
|
||||
|
||||
assuan
|
||||
kbx
|
192
common/errors.h
192
common/errors.h
|
@ -1,192 +0,0 @@
|
|||
/* errors.h - Globally used error codes
|
||||
* Copyright (C) 2001 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#ifndef GNUPG_COMMON_ERRORS_H
|
||||
#define GNUPG_COMMON_ERRORS_H
|
||||
|
||||
#include "util.h"
|
||||
|
||||
/* Error numbers */
|
||||
enum {
|
||||
GNUPG_EOF = -1,
|
||||
GNUPG_No_Error = 0,
|
||||
GNUPG_General_Error = 1,
|
||||
GNUPG_Out_Of_Core = 2,
|
||||
GNUPG_Invalid_Value = 3,
|
||||
GNUPG_IO_Error = 4,
|
||||
GNUPG_Resource_Limit = 5,
|
||||
GNUPG_Internal_Error = 6,
|
||||
GNUPG_Bad_Certificate = 7,
|
||||
GNUPG_Bad_Certificate_Chain = 8,
|
||||
GNUPG_Missing_Certificate = 9,
|
||||
GNUPG_No_Data = 10,
|
||||
GNUPG_Bad_Signature = 11,
|
||||
GNUPG_Not_Implemented = 12,
|
||||
GNUPG_Conflict = 13,
|
||||
GNUPG_Bug = 14,
|
||||
GNUPG_Read_Error = 15,
|
||||
GNUPG_Write_Error = 16,
|
||||
GNUPG_Incomplete_Line = 17,
|
||||
GNUPG_Invalid_Response = 18,
|
||||
GNUPG_No_Agent = 19,
|
||||
GNUPG_Agent_Error = 20,
|
||||
GNUPG_No_Public_Key = 21,
|
||||
GNUPG_No_Secret_Key = 22,
|
||||
GNUPG_File_Open_Error = 23,
|
||||
GNUPG_File_Create_Error = 24,
|
||||
GNUPG_File_Error = 25,
|
||||
GNUPG_Not_Supported = 26,
|
||||
GNUPG_Invalid_Data = 27,
|
||||
GNUPG_Assuan_Server_Fault = 28,
|
||||
GNUPG_Assuan_Error = 29, /* catch all assuan error */
|
||||
GNUPG_Invalid_Session_Key = 30,
|
||||
GNUPG_Invalid_Sexp = 31,
|
||||
GNUPG_Unsupported_Algorithm = 32,
|
||||
GNUPG_No_PIN_Entry = 33,
|
||||
GNUPG_PIN_Entry_Error = 34,
|
||||
GNUPG_Bad_PIN = 35,
|
||||
GNUPG_Bad_Passphrase = 36,
|
||||
GNUPG_Invalid_Name = 37,
|
||||
GNUPG_Bad_Public_Key = 38,
|
||||
GNUPG_Bad_Secret_Key = 39,
|
||||
GNUPG_Bad_Data = 40,
|
||||
GNUPG_Invalid_Parameter = 41,
|
||||
GNUPG_Tribute_to_D_A = 42,
|
||||
GNUPG_No_Dirmngr = 43,
|
||||
GNUPG_Dirmngr_Error = 44,
|
||||
GNUPG_Certificate_Revoked = 45,
|
||||
GNUPG_No_CRL_Known = 46,
|
||||
GNUPG_CRL_Too_Old = 47,
|
||||
GNUPG_Line_Too_Long = 48,
|
||||
GNUPG_Not_Trusted = 49,
|
||||
GNUPG_Canceled = 50,
|
||||
GNUPG_Bad_CA_Certificate = 51,
|
||||
GNUPG_Certificate_Expired = 52,
|
||||
GNUPG_Certificate_Too_Young = 53,
|
||||
GNUPG_Unsupported_Certificate = 54,
|
||||
GNUPG_Unknown_Sexp = 55,
|
||||
GNUPG_Unsupported_Protection = 56,
|
||||
GNUPG_Corrupted_Protection = 57,
|
||||
GNUPG_Ambiguous_Name = 58,
|
||||
GNUPG_Card_Error = 59,
|
||||
GNUPG_Card_Reset = 60,
|
||||
GNUPG_Card_Removed = 61,
|
||||
GNUPG_Invalid_Card = 62,
|
||||
GNUPG_Card_Not_Present = 63,
|
||||
GNUPG_No_PKCS15_App = 64,
|
||||
GNUPG_Not_Confirmed = 65,
|
||||
GNUPG_Configuration_Error = 66,
|
||||
GNUPG_No_Policy_Match = 67,
|
||||
GNUPG_Invalid_Index = 68,
|
||||
GNUPG_Invalid_Id = 69,
|
||||
GNUPG_No_Scdaemon = 70,
|
||||
GNUPG_Scdaemon_Error = 71,
|
||||
GNUPG_Unsupported_Protocol = 72,
|
||||
GNUPG_Bad_PIN_Method = 73,
|
||||
GNUPG_Card_Not_Initialized = 74,
|
||||
GNUPG_Unsupported_Operation = 75,
|
||||
GNUPG_Wrong_Key_Usage = 76,
|
||||
};
|
||||
|
||||
/* Status codes - fixme: should go into another file */
|
||||
enum {
|
||||
STATUS_ENTER,
|
||||
STATUS_LEAVE,
|
||||
STATUS_ABORT,
|
||||
STATUS_GOODSIG,
|
||||
STATUS_BADSIG,
|
||||
STATUS_ERRSIG,
|
||||
STATUS_BADARMOR,
|
||||
STATUS_RSA_OR_IDEA,
|
||||
STATUS_SIGEXPIRED,
|
||||
STATUS_KEYREVOKED,
|
||||
STATUS_TRUST_UNDEFINED,
|
||||
STATUS_TRUST_NEVER,
|
||||
STATUS_TRUST_MARGINAL,
|
||||
STATUS_TRUST_FULLY,
|
||||
STATUS_TRUST_ULTIMATE,
|
||||
|
||||
STATUS_SHM_INFO,
|
||||
STATUS_SHM_GET,
|
||||
STATUS_SHM_GET_BOOL,
|
||||
STATUS_SHM_GET_HIDDEN,
|
||||
|
||||
STATUS_NEED_PASSPHRASE,
|
||||
STATUS_VALIDSIG,
|
||||
STATUS_SIG_ID,
|
||||
STATUS_ENC_TO,
|
||||
STATUS_NODATA,
|
||||
STATUS_BAD_PASSPHRASE,
|
||||
STATUS_NO_PUBKEY,
|
||||
STATUS_NO_SECKEY,
|
||||
STATUS_NEED_PASSPHRASE_SYM,
|
||||
STATUS_DECRYPTION_FAILED,
|
||||
STATUS_DECRYPTION_OKAY,
|
||||
STATUS_MISSING_PASSPHRASE,
|
||||
STATUS_GOOD_PASSPHRASE,
|
||||
STATUS_GOODMDC,
|
||||
STATUS_BADMDC,
|
||||
STATUS_ERRMDC,
|
||||
STATUS_IMPORTED,
|
||||
STATUS_IMPORT_PROBLEM,
|
||||
STATUS_IMPORT_RES,
|
||||
STATUS_FILE_START,
|
||||
STATUS_FILE_DONE,
|
||||
STATUS_FILE_ERROR,
|
||||
|
||||
STATUS_BEGIN_DECRYPTION,
|
||||
STATUS_END_DECRYPTION,
|
||||
STATUS_BEGIN_ENCRYPTION,
|
||||
STATUS_END_ENCRYPTION,
|
||||
|
||||
STATUS_DELETE_PROBLEM,
|
||||
STATUS_GET_BOOL,
|
||||
STATUS_GET_LINE,
|
||||
STATUS_GET_HIDDEN,
|
||||
STATUS_GOT_IT,
|
||||
STATUS_PROGRESS,
|
||||
STATUS_SIG_CREATED,
|
||||
STATUS_SESSION_KEY,
|
||||
STATUS_NOTATION_NAME,
|
||||
STATUS_NOTATION_DATA,
|
||||
STATUS_POLICY_URL,
|
||||
STATUS_BEGIN_STREAM,
|
||||
STATUS_END_STREAM,
|
||||
STATUS_KEY_CREATED,
|
||||
STATUS_USERID_HIN,
|
||||
STATUS_UNEXPECTED,
|
||||
STATUS_INV_RECP,
|
||||
STATUS_NO_RECP,
|
||||
STATUS_ALREADY_SIGNED,
|
||||
|
||||
STATUS_EXPSIG,
|
||||
STATUS_EXPKEYSIG,
|
||||
|
||||
STATUS_TRUNCATED,
|
||||
STATUS_ERROR
|
||||
};
|
||||
|
||||
|
||||
/*-- errors.c (build by mkerror and mkerrtok) --*/
|
||||
const char *gnupg_strerror (int err);
|
||||
const char *gnupg_error_token (int err);
|
||||
|
||||
|
||||
#endif /*GNUPG_COMMON_ERRORS_H*/
|
|
@ -1,87 +0,0 @@
|
|||
/* gettime.c - Wrapper for time functions
|
||||
* Copyright (C) 2002 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
static unsigned long timewarp;
|
||||
static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode;
|
||||
|
||||
/* Wrapper for the time(3). We use this here so we can fake the time
|
||||
for tests */
|
||||
time_t
|
||||
gnupg_get_time ()
|
||||
{
|
||||
time_t current = time (NULL);
|
||||
if (timemode == NORMAL)
|
||||
return current;
|
||||
else if (timemode == FROZEN)
|
||||
return timewarp;
|
||||
else if (timemode == FUTURE)
|
||||
return current + timewarp;
|
||||
else
|
||||
return current - timewarp;
|
||||
}
|
||||
|
||||
/* set the time to NEWTIME so that gnupg_get_time returns a time
|
||||
starting with this one. With FREEZE set to 1 the returned time
|
||||
will never change. Just for completeness, a value of (time_t)-1
|
||||
for NEWTIME gets you back to rality. Note that this is obviously
|
||||
not thread-safe but this is not required. */
|
||||
void
|
||||
gnupg_set_time (time_t newtime, int freeze)
|
||||
{
|
||||
time_t current = time (NULL);
|
||||
|
||||
if ( newtime == (time_t)-1 || current == newtime)
|
||||
{
|
||||
timemode = NORMAL;
|
||||
timewarp = 0;
|
||||
}
|
||||
else if (freeze)
|
||||
{
|
||||
timemode = FROZEN;
|
||||
timewarp = current;
|
||||
}
|
||||
else if (newtime > current)
|
||||
{
|
||||
timemode = FUTURE;
|
||||
timewarp = newtime - current;
|
||||
}
|
||||
else
|
||||
{
|
||||
timemode = PAST;
|
||||
timewarp = current - newtime;
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true when we are in timewarp mode */
|
||||
int
|
||||
gnupg_faked_time_p (void)
|
||||
{
|
||||
return timemode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,271 +0,0 @@
|
|||
/* maperror.c - Error mapping
|
||||
* Copyright (C) 2001, 2002 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ksba.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "errors.h"
|
||||
#include "../assuan/assuan.h"
|
||||
|
||||
/* Note: we might want to wrap this in a macro to get our hands on
|
||||
the line and file where the error occured */
|
||||
int
|
||||
map_ksba_err (int err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case -1:
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case KSBA_Out_Of_Core: err = GNUPG_Out_Of_Core; break;
|
||||
case KSBA_Invalid_Value: err = GNUPG_Invalid_Value; break;
|
||||
case KSBA_Not_Implemented: err = GNUPG_Not_Implemented; break;
|
||||
case KSBA_Conflict: err = GNUPG_Conflict; break;
|
||||
case KSBA_Read_Error: err = GNUPG_Read_Error; break;
|
||||
case KSBA_Write_Error: err = GNUPG_Write_Error; break;
|
||||
case KSBA_No_Data: err = GNUPG_No_Data; break;
|
||||
case KSBA_Bug: err = GNUPG_Bug; break;
|
||||
case KSBA_Unsupported_Algorithm: err = GNUPG_Unsupported_Algorithm; break;
|
||||
case KSBA_Invalid_Index: err = GNUPG_Invalid_Index; break;
|
||||
case KSBA_Invalid_Sexp: err = GNUPG_Invalid_Sexp; break;
|
||||
case KSBA_Unknown_Sexp: err = GNUPG_Unknown_Sexp; break;
|
||||
|
||||
default:
|
||||
err = seterr (General_Error);
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
map_gcry_err (int err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case GCRYERR_EOF:
|
||||
case -1:
|
||||
err = -1;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case GCRYERR_WRONG_PK_ALGO:
|
||||
case GCRYERR_INV_PK_ALGO:
|
||||
case GCRYERR_INV_MD_ALGO:
|
||||
case GCRYERR_INV_CIPHER_ALGO:
|
||||
err = GNUPG_Unsupported_Algorithm;
|
||||
break;
|
||||
case GCRYERR_INV_KEYLEN:
|
||||
case GCRYERR_WEAK_KEY:
|
||||
case GCRYERR_BAD_PUBLIC_KEY: err = GNUPG_Bad_Public_Key; break;
|
||||
case GCRYERR_BAD_SECRET_KEY: err = GNUPG_Bad_Secret_Key; break;
|
||||
case GCRYERR_BAD_SIGNATURE: err = GNUPG_Bad_Signature; break;
|
||||
|
||||
case GCRYERR_BAD_MPI:
|
||||
err = GNUPG_Bad_Data;
|
||||
break;
|
||||
|
||||
case GCRYERR_INV_ARG:
|
||||
case GCRYERR_INV_OP:
|
||||
case GCRYERR_INTERNAL:
|
||||
case GCRYERR_INV_CIPHER_MODE:
|
||||
err = GNUPG_Invalid_Value;
|
||||
break;
|
||||
|
||||
case GCRYERR_SELFTEST:
|
||||
err = GNUPG_Bug;
|
||||
break;
|
||||
|
||||
case GCRYERR_SEXP_INV_LEN_SPEC :
|
||||
case GCRYERR_SEXP_STRING_TOO_LONG :
|
||||
case GCRYERR_SEXP_UNMATCHED_PAREN :
|
||||
case GCRYERR_SEXP_NOT_CANONICAL :
|
||||
case GCRYERR_SEXP_BAD_CHARACTER :
|
||||
case GCRYERR_SEXP_BAD_QUOTATION :
|
||||
case GCRYERR_SEXP_ZERO_PREFIX :
|
||||
case GCRYERR_SEXP_NESTED_DH :
|
||||
case GCRYERR_SEXP_UNMATCHED_DH :
|
||||
case GCRYERR_SEXP_UNEXPECTED_PUNC :
|
||||
case GCRYERR_SEXP_BAD_HEX_CHAR :
|
||||
case GCRYERR_SEXP_ODD_HEX_NUMBERS :
|
||||
case GCRYERR_SEXP_BAD_OCT_CHAR :
|
||||
err = GNUPG_Invalid_Sexp;
|
||||
break;
|
||||
|
||||
case GCRYERR_NO_MEM: err = GNUPG_Out_Of_Core; break;
|
||||
|
||||
case GCRYERR_NOT_IMPL: err = GNUPG_Not_Implemented; break;
|
||||
case GCRYERR_CONFLICT: err = GNUPG_Conflict; break;
|
||||
|
||||
case GCRYERR_INV_OBJ: /* an object is not valid */
|
||||
case GCRYERR_TOO_SHORT: /* provided buffer too short */
|
||||
case GCRYERR_TOO_LARGE: /* object is too large */
|
||||
case GCRYERR_NO_OBJ: /* Missing item in an object */
|
||||
default:
|
||||
err = seterr (General_Error);
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
map_kbx_err (int err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case -1:
|
||||
case 0:
|
||||
break;
|
||||
|
||||
default:
|
||||
err = seterr (General_Error);
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
map_assuan_err (int err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case -1:
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case ASSUAN_Canceled: err = GNUPG_Canceled; break;
|
||||
case ASSUAN_Invalid_Index: err = GNUPG_Invalid_Index; break;
|
||||
|
||||
case ASSUAN_Not_Implemented: err = GNUPG_Not_Implemented; break;
|
||||
case ASSUAN_Server_Fault: err = GNUPG_Assuan_Server_Fault; break;
|
||||
case ASSUAN_No_Public_Key: err = GNUPG_No_Public_Key; break;
|
||||
case ASSUAN_No_Secret_Key: err = GNUPG_No_Secret_Key; break;
|
||||
|
||||
case ASSUAN_Cert_Revoked: err = GNUPG_Certificate_Revoked; break;
|
||||
case ASSUAN_No_CRL_For_Cert: err = GNUPG_No_CRL_Known; break;
|
||||
case ASSUAN_CRL_Too_Old: err = GNUPG_CRL_Too_Old; break;
|
||||
|
||||
case ASSUAN_Not_Trusted: err = GNUPG_Not_Trusted; break;
|
||||
|
||||
case ASSUAN_Card_Error: err = GNUPG_Card_Error; break;
|
||||
case ASSUAN_Invalid_Card: err = GNUPG_Invalid_Card; break;
|
||||
case ASSUAN_No_PKCS15_App: err = GNUPG_No_PKCS15_App; break;
|
||||
case ASSUAN_Card_Not_Present: err= GNUPG_Card_Not_Present; break;
|
||||
case ASSUAN_Not_Confirmed: err = GNUPG_Not_Confirmed; break;
|
||||
case ASSUAN_Invalid_Id: err = GNUPG_Invalid_Id; break;
|
||||
|
||||
default:
|
||||
err = err < 100? GNUPG_Assuan_Server_Fault : GNUPG_Assuan_Error;
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Map GNUPG_xxx error codes to Assuan status codes */
|
||||
int
|
||||
map_to_assuan_status (int rc)
|
||||
{
|
||||
switch (rc)
|
||||
{
|
||||
case -1:
|
||||
rc = ASSUAN_No_Data_Available;
|
||||
break;
|
||||
case 0: break;
|
||||
case GNUPG_Bad_CA_Certificate:
|
||||
case GNUPG_Bad_Certificate:
|
||||
case GNUPG_Wrong_Key_Usage:
|
||||
case GNUPG_Certificate_Revoked:
|
||||
case GNUPG_No_CRL_Known:
|
||||
case GNUPG_CRL_Too_Old:
|
||||
case GNUPG_No_Policy_Match:
|
||||
case GNUPG_Certificate_Expired:
|
||||
rc = ASSUAN_Bad_Certificate;
|
||||
break;
|
||||
case GNUPG_Bad_Certificate_Chain: rc = ASSUAN_Bad_Certificate_Chain; break;
|
||||
case GNUPG_Missing_Certificate: rc = ASSUAN_Missing_Certificate; break;
|
||||
case GNUPG_No_Data: rc = ASSUAN_No_Data_Available; break;
|
||||
case GNUPG_Bad_Signature: rc = ASSUAN_Bad_Signature; break;
|
||||
case GNUPG_Not_Implemented: rc = ASSUAN_Not_Implemented; break;
|
||||
case GNUPG_No_Agent: rc = ASSUAN_No_Agent; break;
|
||||
case GNUPG_Agent_Error: rc = ASSUAN_Agent_Error; break;
|
||||
case GNUPG_No_Public_Key: rc = ASSUAN_No_Public_Key; break;
|
||||
case GNUPG_No_Secret_Key: rc = ASSUAN_No_Secret_Key; break;
|
||||
case GNUPG_Invalid_Data: rc = ASSUAN_Invalid_Data; break;
|
||||
case GNUPG_Invalid_Name: rc = ASSUAN_Invalid_Name; break;
|
||||
case GNUPG_Not_Trusted: rc = ASSUAN_Not_Trusted; break;
|
||||
case GNUPG_Canceled: rc = ASSUAN_Canceled; break;
|
||||
case GNUPG_Invalid_Index: rc = ASSUAN_Invalid_Index; break;
|
||||
|
||||
case GNUPG_Card_Error:
|
||||
case GNUPG_Card_Reset:
|
||||
rc = ASSUAN_Card_Error;
|
||||
break;
|
||||
case GNUPG_Card_Removed:
|
||||
case GNUPG_Card_Not_Present:
|
||||
rc = ASSUAN_Card_Not_Present;
|
||||
break;
|
||||
case GNUPG_Invalid_Card: rc = ASSUAN_Invalid_Card; break;
|
||||
case GNUPG_No_PKCS15_App: rc = ASSUAN_No_PKCS15_App; break;
|
||||
case GNUPG_Not_Confirmed: rc = ASSUAN_Not_Confirmed; break;
|
||||
case GNUPG_Invalid_Id: rc = ASSUAN_Invalid_Id; break;
|
||||
|
||||
case GNUPG_Bad_PIN:
|
||||
case GNUPG_Bad_Passphrase:
|
||||
rc = ASSUAN_No_Secret_Key;
|
||||
break;
|
||||
|
||||
case GNUPG_Read_Error:
|
||||
case GNUPG_Write_Error:
|
||||
case GNUPG_IO_Error:
|
||||
rc = ASSUAN_Server_IO_Error;
|
||||
break;
|
||||
case GNUPG_Out_Of_Core:
|
||||
case GNUPG_Resource_Limit:
|
||||
rc = ASSUAN_Server_Resource_Problem;
|
||||
break;
|
||||
case GNUPG_Bug:
|
||||
case GNUPG_Internal_Error:
|
||||
rc = ASSUAN_Server_Bug;
|
||||
break;
|
||||
default:
|
||||
rc = ASSUAN_Server_Fault;
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
226
common/signal.c
226
common/signal.c
|
@ -1,226 +0,0 @@
|
|||
/* signal.c - signal handling
|
||||
* Copyright (C) 1998, 1999, 2000, 2001, 2002 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
|
||||
static volatile int caught_fatal_sig = 0;
|
||||
static volatile int caught_sigusr1 = 0;
|
||||
static void (*cleanup_fnc)(void);
|
||||
|
||||
|
||||
static void
|
||||
init_one_signal (int sig, RETSIGTYPE (*handler)(int), int check_ign )
|
||||
{
|
||||
#ifndef HAVE_DOSISH_SYSTEM
|
||||
# ifdef HAVE_SIGACTION
|
||||
struct sigaction oact, nact;
|
||||
|
||||
if (check_ign)
|
||||
{
|
||||
/* we don't want to change an IGN handler */
|
||||
sigaction (sig, NULL, &oact );
|
||||
if (oact.sa_handler == SIG_IGN )
|
||||
return;
|
||||
}
|
||||
|
||||
nact.sa_handler = handler;
|
||||
sigemptyset (&nact.sa_mask);
|
||||
nact.sa_flags = 0;
|
||||
sigaction ( sig, &nact, NULL);
|
||||
# else
|
||||
RETSIGTYPE (*ohandler)(int);
|
||||
|
||||
ohandler = signal (sig, handler);
|
||||
if (check_ign && ohandler == SIG_IGN)
|
||||
{
|
||||
/* Change it back if it was already set to IGN */
|
||||
signal (sig, SIG_IGN);
|
||||
}
|
||||
# endif
|
||||
#endif /*!HAVE_DOSISH_SYSTEM*/
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_signal_name( int signum )
|
||||
{
|
||||
#if defined(SYS_SIGLIST_DECLARED) && defined(NSIG)
|
||||
return (signum >= 0 && signum < NSIG) ? sys_siglist[signum] : "?";
|
||||
#else
|
||||
return "some signal";
|
||||
#endif
|
||||
}
|
||||
|
||||
static RETSIGTYPE
|
||||
got_fatal_signal (int sig)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
if (caught_fatal_sig)
|
||||
raise (sig);
|
||||
caught_fatal_sig = 1;
|
||||
|
||||
if (cleanup_fnc)
|
||||
cleanup_fnc ();
|
||||
/* better don't translate these messages */
|
||||
write (2, "\n", 1 );
|
||||
s = log_get_prefix (NULL);
|
||||
if (s)
|
||||
write(2, s, strlen (s));
|
||||
write (2, ": ", 2 );
|
||||
s = get_signal_name(sig);
|
||||
write (2, s, strlen(s) );
|
||||
write (2, " caught ... exiting\n", 20);
|
||||
|
||||
/* reset action to default action and raise signal again */
|
||||
init_one_signal (sig, SIG_DFL, 0);
|
||||
/* fixme: remove_lockfiles ();*/
|
||||
#ifdef __riscos__
|
||||
close_fds ();
|
||||
#endif /* __riscos__ */
|
||||
raise( sig );
|
||||
}
|
||||
|
||||
|
||||
static RETSIGTYPE
|
||||
got_usr_signal (int sig)
|
||||
{
|
||||
caught_sigusr1 = 1;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
gnupg_init_signals (int mode, void (*fast_cleanup)(void))
|
||||
{
|
||||
assert (!mode);
|
||||
|
||||
cleanup_fnc = fast_cleanup;
|
||||
#ifndef HAVE_DOSISH_SYSTEM
|
||||
init_one_signal (SIGINT, got_fatal_signal, 1 );
|
||||
init_one_signal (SIGHUP, got_fatal_signal, 1 );
|
||||
init_one_signal (SIGTERM, got_fatal_signal, 1 );
|
||||
init_one_signal (SIGQUIT, got_fatal_signal, 1 );
|
||||
init_one_signal (SIGSEGV, got_fatal_signal, 1 );
|
||||
init_one_signal (SIGUSR1, got_usr_signal, 0 );
|
||||
init_one_signal (SIGPIPE, SIG_IGN, 0 );
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
gnupg_pause_on_sigusr (int which)
|
||||
{
|
||||
#ifndef HAVE_DOSISH_SYSTEM
|
||||
# ifdef HAVE_SIGPROCMASK
|
||||
sigset_t mask, oldmask;
|
||||
|
||||
assert (which == 1);
|
||||
sigemptyset( &mask );
|
||||
sigaddset( &mask, SIGUSR1 );
|
||||
|
||||
sigprocmask( SIG_BLOCK, &mask, &oldmask );
|
||||
while (!caught_sigusr1)
|
||||
sigsuspend (&oldmask);
|
||||
caught_sigusr1 = 0;
|
||||
sigprocmask (SIG_UNBLOCK, &mask, NULL);
|
||||
# else
|
||||
assert (which == 1);
|
||||
sighold (SIGUSR1);
|
||||
while (!caught_sigusr1)
|
||||
sigpause(SIGUSR1);
|
||||
caught_sigusr1 = 0;
|
||||
sigrelease(SIGUSR1);
|
||||
# endif /*!HAVE_SIGPROCMASK*/
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
do_block( int block )
|
||||
{
|
||||
#ifndef HAVE_DOSISH_SYSTEM
|
||||
static int is_blocked;
|
||||
#ifdef HAVE_SIGPROCMASK
|
||||
static sigset_t oldmask;
|
||||
|
||||
if (block)
|
||||
{
|
||||
sigset_t newmask;
|
||||
|
||||
if (is_blocked)
|
||||
log_bug ("signals are already blocked\n");
|
||||
sigfillset( &newmask );
|
||||
sigprocmask( SIG_BLOCK, &newmask, &oldmask );
|
||||
is_blocked = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_blocked)
|
||||
log_bug("signals are not blocked\n");
|
||||
sigprocmask (SIG_SETMASK, &oldmask, NULL);
|
||||
is_blocked = 0;
|
||||
}
|
||||
#else /*!HAVE_SIGPROCMASK*/
|
||||
static void (*disposition[MAXSIG])();
|
||||
int sig;
|
||||
|
||||
if (block)
|
||||
{
|
||||
if (is_blocked)
|
||||
log_bug("signals are already blocked\n");
|
||||
for (sig=1; sig < MAXSIG; sig++)
|
||||
{
|
||||
disposition[sig] = sigset (sig, SIG_HOLD);
|
||||
}
|
||||
is_blocked = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_blocked)
|
||||
log_bug ("signals are not blocked\n");
|
||||
for (sig=1; sig < MAXSIG; sig++) {
|
||||
sigset (sig, disposition[sig]);
|
||||
}
|
||||
is_blocked = 0;
|
||||
}
|
||||
#endif /*!HAVE_SIGPROCMASK*/
|
||||
#endif /*HAVE_DOSISH_SYSTEM*/
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
gnupg_block_all_signals ()
|
||||
{
|
||||
do_block(1);
|
||||
}
|
||||
|
||||
void
|
||||
gnupg_unblock_all_signals ()
|
||||
{
|
||||
do_block(0);
|
||||
}
|
117
common/util.h
117
common/util.h
|
@ -1,117 +0,0 @@
|
|||
/* util.h - Utility functions for Gnupg
|
||||
* Copyright (C) 2001, 2002 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
|
||||
#ifndef GNUPG_COMMON_UTIL_H
|
||||
#define GNUPG_COMMON_UTIL_H
|
||||
|
||||
#include <gcrypt.h> /* we need this for the memory function protos */
|
||||
#include <time.h> /* we need time_t */
|
||||
|
||||
/* to pass hash functions to libksba we need to cast it */
|
||||
#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
|
||||
|
||||
/* get all the stuff from jnlib */
|
||||
#include "../jnlib/logging.h"
|
||||
#include "../jnlib/argparse.h"
|
||||
#include "../jnlib/stringhelp.h"
|
||||
#include "../jnlib/mischelp.h"
|
||||
#include "../jnlib/strlist.h"
|
||||
#include "../jnlib/dotlock.h"
|
||||
|
||||
/* handy malloc macros - use only them */
|
||||
#define xtrymalloc(a) gcry_malloc ((a))
|
||||
#define xtrycalloc(a,b) gcry_calloc ((a),(b))
|
||||
#define xtryrealloc(a,b) gcry_realloc ((a),(b))
|
||||
#define xtrystrdup(a) gcry_strdup ((a))
|
||||
#define xfree(a) gcry_free ((a))
|
||||
|
||||
#define xmalloc(a) gcry_xmalloc ((a))
|
||||
#define xcalloc(a,b) gcry_xcalloc ((a),(b))
|
||||
#define xrealloc(a,b) gcry_xrealloc ((a),(b))
|
||||
#define xstrdup(a) gcry_xstrdup ((a))
|
||||
|
||||
#define seterr(a) (GNUPG_ ## a)
|
||||
|
||||
/*-- maperror.c --*/
|
||||
int map_ksba_err (int err);
|
||||
int map_gcry_err (int err);
|
||||
int map_kbx_err (int err);
|
||||
int map_assuan_err (int err);
|
||||
int map_to_assuan_status (int rc);
|
||||
|
||||
/*-- gettime.c --*/
|
||||
time_t gnupg_get_time (void);
|
||||
void gnupg_set_time (time_t newtime, int freeze);
|
||||
int gnupg_faked_time_p (void);
|
||||
|
||||
/*-- signal.c --*/
|
||||
void gnupg_init_signals (int mode, void (*fast_cleanup)(void));
|
||||
void gnupg_pause_on_sigusr (int which);
|
||||
void gnupg_block_all_signals (void);
|
||||
void gnupg_unblock_all_signals (void);
|
||||
|
||||
|
||||
/*-- replacement functions from funcname.c --*/
|
||||
#if !HAVE_VASPRINTF
|
||||
#include <stdarg.h>
|
||||
int vasprintf (char **result, const char *format, va_list *args);
|
||||
int asprintf (char **result, const char *format, ...);
|
||||
#endif
|
||||
|
||||
#if !HAVE_FOPENCOOKIE
|
||||
typedef struct
|
||||
{
|
||||
ssize_t (*read)(void*,char*,size_t);
|
||||
ssize_t (*write)(void*,const char*,size_t);
|
||||
int (*seek)(void*,off_t*,int);
|
||||
int (*close)(void*);
|
||||
} _IO_cookie_io_functions_t;
|
||||
typedef _IO_cookie_io_functions_t cookie_io_functions_t;
|
||||
FILE *fopencookie (void *cookie, const char *opentype,
|
||||
cookie_io_functions_t funclist);
|
||||
#endif /*!HAVE_FOPENCOOKIE*/
|
||||
|
||||
|
||||
|
||||
/*-- some macros to replace ctype ones and avoid locale problems --*/
|
||||
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
|
||||
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
|
||||
#define hexdigitp(a) (digitp (a) \
|
||||
|| (*(a) >= 'A' && *(a) <= 'F') \
|
||||
|| (*(a) >= 'a' && *(a) <= 'f'))
|
||||
/* the atoi macros assume that the buffer has only valid digits */
|
||||
#define atoi_1(p) (*(p) - '0' )
|
||||
#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1))
|
||||
#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2))
|
||||
#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 /*GNUPG_COMMON_UTIL_H*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue