mirror of
git://git.gnupg.org/gnupg.git
synced 2025-07-02 22:46:30 +02:00
Merge branch 'STABLE-BRANCH-2-4'
-- Fixed conflicts: NEWS configure.ac doc/gpg.texi
This commit is contained in:
commit
dfa60c09f5
88 changed files with 2655 additions and 1419 deletions
|
@ -1530,14 +1530,16 @@ scd_readkey (const char *keyrefstr, int create_shadow, gcry_sexp_t *r_result)
|
|||
unsigned char *buf;
|
||||
size_t len, buflen;
|
||||
|
||||
*r_result = NULL;
|
||||
if (r_result)
|
||||
*r_result = NULL;
|
||||
err = start_agent (0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
init_membuf (&data, 1024);
|
||||
if (create_shadow)
|
||||
snprintf (line, DIM(line), "READKEY --card -- %s", keyrefstr);
|
||||
snprintf (line, DIM(line), "READKEY %s--card -- %s",
|
||||
r_result? "" : "--no-data ", keyrefstr);
|
||||
else
|
||||
snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
|
||||
err = assuan_transact (agent_ctx, line,
|
||||
|
@ -1553,7 +1555,7 @@ scd_readkey (const char *keyrefstr, int create_shadow, gcry_sexp_t *r_result)
|
|||
if (!buf)
|
||||
return gpg_error_from_syserror ();
|
||||
|
||||
err = gcry_sexp_new (r_result, buf, buflen, 0);
|
||||
err = r_result ? gcry_sexp_new (r_result, buf, buflen, 0) : 0;
|
||||
xfree (buf);
|
||||
|
||||
return err;
|
||||
|
@ -1770,6 +1772,90 @@ agent_get_s2k_count (void)
|
|||
}
|
||||
|
||||
|
||||
|
||||
struct havekey_status_parm_s
|
||||
{
|
||||
char *string;
|
||||
};
|
||||
|
||||
static gpg_error_t
|
||||
havekey_status_cb (void *opaque, const char *line)
|
||||
{
|
||||
struct havekey_status_parm_s *parm = opaque;
|
||||
const char *s;
|
||||
char *p;
|
||||
|
||||
if ((s = has_leading_keyword (line, "KEYFILEINFO")))
|
||||
{
|
||||
xfree (parm->string);
|
||||
parm->string = xtrystrdup (s);
|
||||
if (!parm->string)
|
||||
return gpg_error_from_syserror ();
|
||||
p = strchr (parm->string, ' ');
|
||||
if (p)
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Run the HAVEKEY --info command and stores the retrieved string at
|
||||
* R_RESULT. Caller must free that string. If an error is returned
|
||||
* R_RESULT is set to NULL. */
|
||||
gpg_error_t
|
||||
scd_havekey_info (const unsigned char *grip, char **r_result)
|
||||
{
|
||||
gpg_error_t err;
|
||||
char line[ASSUAN_LINELENGTH];
|
||||
struct havekey_status_parm_s parm = {NULL};
|
||||
|
||||
*r_result = NULL;
|
||||
|
||||
err = start_agent (0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
snprintf (line, sizeof line, "HAVEKEY --info ");
|
||||
log_assert (ASSUAN_LINELENGTH > strlen(line) + 2*KEYGRIP_LEN + 10);
|
||||
bin2hex (grip, KEYGRIP_LEN, line+strlen(line));
|
||||
|
||||
err = assuan_transact (agent_ctx, line,
|
||||
NULL, NULL, NULL, NULL,
|
||||
havekey_status_cb, &parm);
|
||||
if (err)
|
||||
xfree (parm.string);
|
||||
else
|
||||
*r_result = parm.string;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Run the DELETE_KEY command. If FORCE is given the user will not be
|
||||
* asked for confirmation. */
|
||||
gpg_error_t
|
||||
scd_delete_key (const unsigned char *grip, int force)
|
||||
{
|
||||
gpg_error_t err;
|
||||
char line[ASSUAN_LINELENGTH];
|
||||
struct default_inq_parm_s dfltparm = {NULL};
|
||||
|
||||
err = start_agent (0);
|
||||
if (err)
|
||||
return err;
|
||||
dfltparm.ctx = agent_ctx;
|
||||
|
||||
snprintf (line, sizeof line, "DELETE_KEY%s ", force?" --force":"");
|
||||
log_assert (ASSUAN_LINELENGTH > strlen(line) + 2*KEYGRIP_LEN + 10);
|
||||
bin2hex (grip, KEYGRIP_LEN, line+strlen(line));
|
||||
|
||||
err = assuan_transact (agent_ctx, line,
|
||||
NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Return a malloced string describing the statusword SW. On error
|
||||
* NULL is returned. */
|
||||
char *
|
||||
|
|
142
tools/gpg-card.c
142
tools/gpg-card.c
|
@ -582,13 +582,14 @@ print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
|
|||
|
||||
/* Print the keygrip GRP. */
|
||||
static void
|
||||
print_keygrip (estream_t fp, const unsigned char *grp)
|
||||
print_keygrip (estream_t fp, const unsigned char *grp, int with_lf)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < 20 ; i++, grp++)
|
||||
tty_fprintf (fp, "%02X", *grp);
|
||||
tty_fprintf (fp, "\n");
|
||||
if (with_lf)
|
||||
tty_fprintf (fp, "\n");
|
||||
}
|
||||
|
||||
|
||||
|
@ -700,7 +701,7 @@ list_one_kinfo (card_info_t info, key_info_t kinfo,
|
|||
goto leave;
|
||||
}
|
||||
|
||||
print_keygrip (fp, kinfo->grip);
|
||||
print_keygrip (fp, kinfo->grip, 1);
|
||||
tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
|
||||
if (kinfo->usage)
|
||||
{
|
||||
|
@ -1376,6 +1377,137 @@ cmd_list (card_info_t info, char *argstr)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/* The CHECKKEYS command. */
|
||||
static gpg_error_t
|
||||
cmd_checkkeys (card_info_t callerinfo, char *argstr)
|
||||
{
|
||||
gpg_error_t err;
|
||||
estream_t fp = opt.interactive? NULL : es_stdout;
|
||||
strlist_t cards = NULL;
|
||||
strlist_t sl;
|
||||
int opt_ondisk;
|
||||
int opt_delete_clear;
|
||||
int opt_delete_protected;
|
||||
int delete_count = 0;
|
||||
struct card_info_s info_buffer = { 0 };
|
||||
card_info_t info = &info_buffer;
|
||||
key_info_t kinfo;
|
||||
|
||||
|
||||
if (!callerinfo)
|
||||
return print_help
|
||||
("CHECKKEYS [--ondisk] [--delete-clear-copy] [--delete-protected-copy]"
|
||||
"\n\n"
|
||||
"Print a list of keys on all inserted cards. With --ondisk only\n"
|
||||
"keys are listed which also have a copy on disk. Missing shadow\n"
|
||||
"keys are created. With --delete-clear-copy, copies of keys also\n"
|
||||
"stored on disk without any protection will be deleted.\n"
|
||||
, 0);
|
||||
|
||||
|
||||
opt_ondisk = has_leading_option (argstr, "--ondisk");
|
||||
opt_delete_clear = has_leading_option (argstr, "--delete-clear-copy");
|
||||
opt_delete_protected = has_leading_option (argstr, "--delete-protected-copy");
|
||||
argstr = skip_options (argstr);
|
||||
|
||||
if (*argstr)
|
||||
{
|
||||
/* No args expected */
|
||||
err = gpg_error (GPG_ERR_INV_ARG);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
if (!callerinfo->serialno)
|
||||
{
|
||||
/* This is probably the first call We need to send a SERIALNO
|
||||
* command to scdaemon so that our session knows all cards. */
|
||||
err = scd_serialno (NULL, NULL);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
|
||||
/* Get the list of all cards. */
|
||||
err = scd_cardlist (&cards);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* Loop over all cards. We use our own info buffer here. */
|
||||
for (sl = cards; sl; sl = sl->next)
|
||||
{
|
||||
err = scd_switchcard (sl->d);
|
||||
if (err)
|
||||
{
|
||||
log_error ("Error switching to card %s: %s\n",
|
||||
sl->d, gpg_strerror (err));
|
||||
continue;
|
||||
}
|
||||
release_card_info (info);
|
||||
err = scd_learn (info, 0);
|
||||
if (err)
|
||||
{
|
||||
log_error ("Error getting infos from card %s: %s\n",
|
||||
sl->d, gpg_strerror (err));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
|
||||
{
|
||||
char *infostr;
|
||||
|
||||
err = scd_havekey_info (kinfo->grip, &infostr);
|
||||
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
|
||||
{
|
||||
/* Create a shadow key and try again. */
|
||||
scd_readkey (kinfo->keyref, 1, NULL);
|
||||
err = scd_havekey_info (kinfo->grip, &infostr);
|
||||
}
|
||||
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
|
||||
log_error ("Error getting infos for a key: %s\n",
|
||||
gpg_strerror (err));
|
||||
|
||||
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
|
||||
; /* does not make sense to show this. */
|
||||
else if (opt_ondisk && infostr && !strcmp (infostr, "shadowed"))
|
||||
; /* Don't print this one. */
|
||||
else
|
||||
{
|
||||
tty_fprintf (fp, "%s %s ",
|
||||
nullnone (info->serialno),
|
||||
app_type_string (info->apptype));
|
||||
print_keygrip (fp, kinfo->grip, 0);
|
||||
tty_fprintf (fp, " %s %s\n",
|
||||
kinfo->keyref, infostr? infostr: "error");
|
||||
}
|
||||
if (infostr
|
||||
&& ((opt_delete_clear && !strcmp (infostr, "clear"))
|
||||
|| (opt_delete_protected && !strcmp (infostr, "protected"))))
|
||||
{
|
||||
err = scd_delete_key (kinfo->grip, 0);
|
||||
if (err)
|
||||
log_error ("Error deleting a key copy: %s\n",
|
||||
gpg_strerror (err));
|
||||
else
|
||||
delete_count++;
|
||||
}
|
||||
xfree (infostr);
|
||||
}
|
||||
}
|
||||
es_fflush (es_stdout);
|
||||
if (delete_count)
|
||||
log_info ("Number of deleted key copies: %d\n", delete_count);
|
||||
|
||||
err = 0;
|
||||
|
||||
leave:
|
||||
release_card_info (info);
|
||||
free_strlist (cards);
|
||||
/* Better reset to the original card. */
|
||||
scd_learn (callerinfo, 0);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* The VERIFY command. */
|
||||
static gpg_error_t
|
||||
|
@ -3728,6 +3860,7 @@ enum cmdids
|
|||
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
|
||||
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
|
||||
cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY,
|
||||
cmdCHECKKEYS,
|
||||
cmdINVCMD
|
||||
};
|
||||
|
||||
|
@ -3767,6 +3900,7 @@ static struct
|
|||
{ "readcert", cmdREADCERT, N_("read a certificate from a data object")},
|
||||
{ "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
|
||||
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
|
||||
{ "checkkeys", cmdCHECKKEYS, N_("run various checks on the keys")},
|
||||
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
|
||||
{ "gpg", cmdGPG, NULL},
|
||||
{ "gpgsm", cmdGPGSM, NULL},
|
||||
|
@ -3903,6 +4037,7 @@ dispatch_command (card_info_t info, const char *orig_command)
|
|||
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
|
||||
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
|
||||
case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */
|
||||
case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
|
||||
|
||||
case cmdINVCMD:
|
||||
default:
|
||||
|
@ -4162,6 +4297,7 @@ interactive_loop (void)
|
|||
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
|
||||
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
|
||||
case cmdHISTORY: err = cmd_history (info, argstr); break;
|
||||
case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
|
||||
|
||||
case cmdINVCMD:
|
||||
default:
|
||||
|
|
|
@ -246,6 +246,8 @@ gpg_error_t scd_cardlist (strlist_t *result);
|
|||
gpg_error_t scd_applist (strlist_t *result, int all);
|
||||
gpg_error_t scd_change_pin (const char *pinref, int reset_mode, int nullpin);
|
||||
gpg_error_t scd_checkpin (const char *serialno);
|
||||
gpg_error_t scd_havekey_info (const unsigned char *grip, char **r_result);
|
||||
gpg_error_t scd_delete_key (const unsigned char *grip, int force);
|
||||
|
||||
unsigned long agent_get_s2k_count (void);
|
||||
|
||||
|
|
126
tools/gpgconf.c
126
tools/gpgconf.c
|
@ -86,30 +86,31 @@ enum cmd_and_opt_values
|
|||
/* The list of commands and options. */
|
||||
static gpgrt_opt_t opts[] =
|
||||
{
|
||||
{ 300, NULL, 0, N_("@Commands:\n ") },
|
||||
ARGPARSE_group (300, N_("@Commands:\n ")),
|
||||
|
||||
{ aListComponents, "list-components", 256, N_("list all components") },
|
||||
{ aCheckPrograms, "check-programs", 256, N_("check all programs") },
|
||||
{ aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
|
||||
{ aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") },
|
||||
{ aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") },
|
||||
{ aApplyDefaults, "apply-defaults", 256,
|
||||
N_("apply global default values") },
|
||||
{ aApplyProfile, "apply-profile", 256,
|
||||
N_("|FILE|update configuration files using FILE") },
|
||||
{ aListDirs, "list-dirs", 256,
|
||||
N_("get the configuration directories for @GPGCONF@") },
|
||||
{ aListConfig, "list-config", 256,
|
||||
N_("list global configuration file") },
|
||||
{ aCheckConfig, "check-config", 256,
|
||||
N_("check global configuration file") },
|
||||
{ aQuerySWDB, "query-swdb", 256,
|
||||
N_("query the software version database") },
|
||||
{ aReload, "reload", 256, N_("reload all or a given component")},
|
||||
{ aLaunch, "launch", 256, N_("launch a given component")},
|
||||
{ aKill, "kill", 256, N_("kill a given component")},
|
||||
{ aCreateSocketDir, "create-socketdir", 256, "@"},
|
||||
{ aRemoveSocketDir, "remove-socketdir", 256, "@"},
|
||||
ARGPARSE_c (aListComponents, "list-components", N_("list all components")),
|
||||
ARGPARSE_c (aCheckPrograms, "check-programs", N_("check all programs")),
|
||||
ARGPARSE_c (aListOptions, "list-options", N_("|COMPONENT|list options")),
|
||||
ARGPARSE_c (aChangeOptions, "change-options",
|
||||
N_("|COMPONENT|change options")),
|
||||
ARGPARSE_c (aCheckOptions, "check-options", N_("|COMPONENT|check options")),
|
||||
ARGPARSE_c (aApplyDefaults, "apply-defaults",
|
||||
N_("apply global default values")),
|
||||
ARGPARSE_c (aApplyProfile, "apply-profile",
|
||||
N_("|FILE|update configuration files using FILE")),
|
||||
ARGPARSE_c (aListDirs, "list-dirs",
|
||||
N_("get the configuration directories for @GPGCONF@")),
|
||||
ARGPARSE_c (aListConfig, "list-config",
|
||||
N_("list global configuration file")),
|
||||
ARGPARSE_c (aCheckConfig, "check-config",
|
||||
N_("check global configuration file")),
|
||||
ARGPARSE_c (aQuerySWDB, "query-swdb",
|
||||
N_("query the software version database")),
|
||||
ARGPARSE_c (aReload, "reload", N_("reload all or a given component")),
|
||||
ARGPARSE_c (aLaunch, "launch", N_("launch a given component")),
|
||||
ARGPARSE_c (aKill, "kill", N_("kill a given component")),
|
||||
ARGPARSE_c (aCreateSocketDir, "create-socketdir", "@"),
|
||||
ARGPARSE_c (aRemoveSocketDir, "remove-socketdir", "@"),
|
||||
ARGPARSE_c (aShowVersions, "show-versions", ""),
|
||||
ARGPARSE_c (aShowConfigs, "show-configs", ""),
|
||||
/* hidden commands: for debugging */
|
||||
|
@ -117,24 +118,25 @@ static gpgrt_opt_t opts[] =
|
|||
ARGPARSE_c (aDotlockLock, "lock", "@"),
|
||||
ARGPARSE_c (aDotlockUnlock, "unlock", "@"),
|
||||
|
||||
{ 301, NULL, 0, N_("@\nOptions:\n ") },
|
||||
ARGPARSE_header (NULL, N_("@\nOptions:\n ")),
|
||||
|
||||
{ oOutput, "output", 2, N_("use as output file") },
|
||||
{ oVerbose, "verbose", 0, N_("verbose") },
|
||||
{ oQuiet, "quiet", 0, N_("quiet") },
|
||||
{ oDryRun, "dry-run", 0, N_("do not make any changes") },
|
||||
{ oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") },
|
||||
ARGPARSE_s_s (oOutput, "output", N_("use as output file")),
|
||||
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
|
||||
ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")),
|
||||
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
|
||||
ARGPARSE_s_n (oRuntime, "runtime",
|
||||
N_("activate changes at runtime, if possible")),
|
||||
ARGPARSE_s_i (oStatusFD, "status-fd",
|
||||
N_("|FD|write status info to this FD")),
|
||||
/* hidden options */
|
||||
{ oHomedir, "homedir", 2, "@" },
|
||||
{ oBuilddir, "build-prefix", 2, "@" },
|
||||
{ oNull, "null", 0, "@" },
|
||||
{ oNoVerbose, "no-verbose", 0, "@"},
|
||||
ARGPARSE_s_s (oHomedir, "homedir", "@"),
|
||||
ARGPARSE_s_s (oBuilddir, "build-prefix", "@"),
|
||||
ARGPARSE_s_n (oNull, "null", "@"),
|
||||
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
|
||||
ARGPARSE_s_n (oShowSocket, "show-socket", "@"),
|
||||
ARGPARSE_s_s (oChUid, "chuid", "@"),
|
||||
|
||||
ARGPARSE_end(),
|
||||
ARGPARSE_end ()
|
||||
};
|
||||
|
||||
|
||||
|
@ -1072,12 +1074,12 @@ main (int argc, char **argv)
|
|||
#if !defined(HAVE_W32_SYSTEM)
|
||||
if (!fname)
|
||||
{
|
||||
es_fprintf (es_stderr, "usage: %s [options] lock|unlock NAME",
|
||||
GPGCONF_NAME);
|
||||
es_fprintf (es_stderr, "usage: %s --%slock NAME",
|
||||
GPGCONF_NAME, cmd==aDotlockUnlock?"un":"");
|
||||
es_putc ('\n', es_stderr);
|
||||
es_fputs (_("Need one NAME argument"), es_stderr);
|
||||
es_fputs ("Need name of file protected by the lock", es_stderr);
|
||||
es_putc ('\n', es_stderr);
|
||||
gpgconf_failure (GPG_ERR_USER_2);
|
||||
gpgconf_failure (GPG_ERR_SYNTAX);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1151,10 +1153,12 @@ get_revision_from_blurb (const char *blurb, int *r_len)
|
|||
static void
|
||||
show_version_gnupg (estream_t fp, const char *prefix)
|
||||
{
|
||||
char *fname, *p;
|
||||
char *fname, *p, *p0;
|
||||
size_t n;
|
||||
estream_t verfp;
|
||||
char line[100];
|
||||
char *line = NULL;
|
||||
size_t line_len = 0;
|
||||
ssize_t length;
|
||||
|
||||
es_fprintf (fp, "%s%sGnuPG %s (%s)\n%s%s\n", prefix, *prefix?"":"* ",
|
||||
gpgrt_strusage (13), BUILD_REVISION, prefix, gpgrt_strusage (17));
|
||||
|
@ -1173,20 +1177,46 @@ show_version_gnupg (estream_t fp, const char *prefix)
|
|||
verfp = es_fopen (fname, "r");
|
||||
if (!verfp)
|
||||
es_fprintf (fp, "%s[VERSION file not found]\n", prefix);
|
||||
else if (!es_fgets (line, sizeof line, verfp))
|
||||
es_fprintf (fp, "%s[VERSION file is empty]\n", prefix);
|
||||
else
|
||||
{
|
||||
trim_spaces (line);
|
||||
for (p=line; *p; p++)
|
||||
if (*p < ' ' || *p > '~' || *p == '[')
|
||||
*p = '?';
|
||||
es_fprintf (fp, "%s%s\n", prefix, line);
|
||||
int lnr = 0;
|
||||
|
||||
p0 = NULL;
|
||||
while ((length = es_read_line (verfp, &line, &line_len, NULL))>0)
|
||||
{
|
||||
lnr++;
|
||||
trim_spaces (line);
|
||||
if (lnr == 1 && *line != '[')
|
||||
{
|
||||
/* Old file format where we look only at the
|
||||
* first line. */
|
||||
p0 = line;
|
||||
break;
|
||||
}
|
||||
else if (!strncmp (line, "version=", 8))
|
||||
{
|
||||
p0 = line + 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (length < 0 || es_ferror (verfp))
|
||||
es_fprintf (fp, "%s[VERSION file read error]\n", prefix);
|
||||
else if (p0)
|
||||
{
|
||||
for (p=p0; *p; p++)
|
||||
if (*p < ' ' || *p > '~' || *p == '[')
|
||||
*p = '?';
|
||||
es_fprintf (fp, "%s%s\n", prefix, p0);
|
||||
}
|
||||
else
|
||||
es_fprintf (fp, "%s[VERSION file is empty]\n", prefix);
|
||||
|
||||
es_fclose (verfp);
|
||||
}
|
||||
es_fclose (verfp);
|
||||
}
|
||||
xfree (fname);
|
||||
}
|
||||
xfree (line);
|
||||
|
||||
#ifdef HAVE_W32_SYSTEM
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue