Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34109937
wks-util.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
30 KB
Subscribers
None
wks-util.c
View Options
/* wks-utils.c - Common helper functions for wks tools
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*/
#include
<config.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<unistd.h>
#include
"../common/util.h"
#include
"../common/status.h"
#include
"../common/ccparray.h"
#include
"../common/exectool.h"
#include
"../common/zb32.h"
#include
"../common/userids.h"
#include
"../common/mbox-util.h"
#include
"../common/sysutils.h"
#include
"mime-maker.h"
#include
"send-mail.h"
#include
"gpg-wks.h"
/* The stream to output the status information. Output is disabled if
this is NULL. */
static
estream_t
statusfp
;
/* Set the status FD. */
void
wks_set_status_fd
(
int
fd
)
{
static
int
last_fd
=
-1
;
if
(
fd
!=
-1
&&
last_fd
==
fd
)
return
;
if
(
statusfp
&&
statusfp
!=
es_stdout
&&
statusfp
!=
es_stderr
)
es_fclose
(
statusfp
);
statusfp
=
NULL
;
if
(
fd
==
-1
)
return
;
if
(
fd
==
1
)
statusfp
=
es_stdout
;
else
if
(
fd
==
2
)
statusfp
=
es_stderr
;
else
statusfp
=
es_fdopen
(
fd
,
"w"
);
if
(
!
statusfp
)
{
log_fatal
(
"can't open fd %d for status output: %s
\n
"
,
fd
,
gpg_strerror
(
gpg_error_from_syserror
()));
}
last_fd
=
fd
;
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
wks_write_status
(
int
no
,
const
char
*
format
,
...)
{
va_list
arg_ptr
;
if
(
!
statusfp
)
return
;
/* Not enabled. */
es_fputs
(
"[GNUPG:] "
,
statusfp
);
es_fputs
(
get_status_string
(
no
),
statusfp
);
if
(
format
)
{
es_putc
(
' '
,
statusfp
);
va_start
(
arg_ptr
,
format
);
es_vfprintf
(
statusfp
,
format
,
arg_ptr
);
va_end
(
arg_ptr
);
}
es_putc
(
'\n'
,
statusfp
);
}
/* Append UID to LIST and return the new item. On success LIST is
* updated. C-style escaping is removed from UID. On error ERRNO is
* set and NULL returned. */
static
uidinfo_list_t
append_to_uidinfo_list
(
uidinfo_list_t
*
list
,
const
char
*
uid
,
time_t
created
)
{
uidinfo_list_t
r
,
sl
;
char
*
plainuid
;
plainuid
=
decode_c_string
(
uid
);
if
(
!
plainuid
)
return
NULL
;
sl
=
xtrymalloc
(
sizeof
*
sl
+
strlen
(
plainuid
));
if
(
!
sl
)
{
xfree
(
plainuid
);
return
NULL
;
}
strcpy
(
sl
->
uid
,
plainuid
);
sl
->
created
=
created
;
sl
->
mbox
=
mailbox_from_userid
(
plainuid
,
0
);
sl
->
next
=
NULL
;
if
(
!*
list
)
*
list
=
sl
;
else
{
for
(
r
=
*
list
;
r
->
next
;
r
=
r
->
next
)
;
r
->
next
=
sl
;
}
xfree
(
plainuid
);
return
sl
;
}
/* Free the list of uid infos at LIST. */
void
free_uidinfo_list
(
uidinfo_list_t
list
)
{
while
(
list
)
{
uidinfo_list_t
tmp
=
list
->
next
;
xfree
(
list
->
mbox
);
xfree
(
list
);
list
=
tmp
;
}
}
struct
get_key_status_parm_s
{
const
char
*
fpr
;
int
found
;
int
count
;
};
static
void
get_key_status_cb
(
void
*
opaque
,
const
char
*
keyword
,
char
*
args
)
{
struct
get_key_status_parm_s
*
parm
=
opaque
;
/*log_debug ("%s: %s\n", keyword, args);*/
if
(
!
strcmp
(
keyword
,
"EXPORTED"
))
{
parm
->
count
++
;
if
(
!
ascii_strcasecmp
(
args
,
parm
->
fpr
))
parm
->
found
=
1
;
}
}
/* Get a key by fingerprint from gpg's keyring and make sure that the
* mail address ADDRSPEC is included in the key. If EXACT is set the
* returned user id must match Addrspec exactly and not just in the
* addr-spec (mailbox) part. The key is returned as a new memory
* stream at R_KEY. */
gpg_error_t
wks_get_key
(
estream_t
*
r_key
,
const
char
*
fingerprint
,
const
char
*
addrspec
,
int
exact
)
{
gpg_error_t
err
;
ccparray_t
ccp
;
const
char
**
argv
=
NULL
;
estream_t
key
=
NULL
;
struct
get_key_status_parm_s
parm
;
char
*
filterexp
=
NULL
;
memset
(
&
parm
,
0
,
sizeof
parm
);
*
r_key
=
NULL
;
key
=
es_fopenmem
(
0
,
"w+b"
);
if
(
!
key
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error allocating memory buffer: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
/* Prefix the key with the MIME content type. */
es_fputs
(
"Content-Type: application/pgp-keys
\n
"
"
\n
"
,
key
);
filterexp
=
es_bsprintf
(
"keep-uid=%s= %s"
,
exact
?
"uid"
:
"mbox"
,
addrspec
);
if
(
!
filterexp
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error allocating memory buffer: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
ccparray_init
(
&
ccp
,
0
);
ccparray_put
(
&
ccp
,
"--no-options"
);
if
(
!
opt
.
verbose
)
ccparray_put
(
&
ccp
,
"--quiet"
);
else
if
(
opt
.
verbose
>
1
)
ccparray_put
(
&
ccp
,
"--verbose"
);
ccparray_put
(
&
ccp
,
"--batch"
);
ccparray_put
(
&
ccp
,
"--status-fd=2"
);
ccparray_put
(
&
ccp
,
"--always-trust"
);
ccparray_put
(
&
ccp
,
"--armor"
);
ccparray_put
(
&
ccp
,
"--export-options=export-minimal"
);
ccparray_put
(
&
ccp
,
"--export-filter"
);
ccparray_put
(
&
ccp
,
filterexp
);
ccparray_put
(
&
ccp
,
"--export"
);
ccparray_put
(
&
ccp
,
"--"
);
ccparray_put
(
&
ccp
,
fingerprint
);
ccparray_put
(
&
ccp
,
NULL
);
argv
=
ccparray_get
(
&
ccp
,
NULL
);
if
(
!
argv
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
parm
.
fpr
=
fingerprint
;
err
=
gnupg_exec_tool_stream
(
opt
.
gpg_program
,
argv
,
NULL
,
NULL
,
key
,
get_key_status_cb
,
&
parm
);
if
(
!
err
&&
parm
.
count
>
1
)
err
=
gpg_error
(
GPG_ERR_TOO_MANY
);
else
if
(
!
err
&&
!
parm
.
found
)
err
=
gpg_error
(
GPG_ERR_NOT_FOUND
);
if
(
err
)
{
log_error
(
"export failed: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
es_rewind
(
key
);
*
r_key
=
key
;
key
=
NULL
;
leave
:
es_fclose
(
key
);
xfree
(
argv
);
xfree
(
filterexp
);
return
err
;
}
/* Helper for wks_list_key and wks_filter_uid. */
static
void
key_status_cb
(
void
*
opaque
,
const
char
*
keyword
,
char
*
args
)
{
(
void
)
opaque
;
if
(
DBG_CRYPTO
)
log_debug
(
"gpg status: %s %s
\n
"
,
keyword
,
args
);
}
/* Run gpg on KEY and store the primary fingerprint at R_FPR and the
* list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL
* is stored at R_FPR and R_MBOXES and an error code is returned.
* R_FPR may be NULL if the fingerprint is not needed. */
gpg_error_t
wks_list_key
(
estream_t
key
,
char
**
r_fpr
,
uidinfo_list_t
*
r_mboxes
)
{
gpg_error_t
err
;
ccparray_t
ccp
;
const
char
**
argv
;
estream_t
listing
;
char
*
line
=
NULL
;
size_t
length_of_line
=
0
;
size_t
maxlen
;
ssize_t
len
;
char
**
fields
=
NULL
;
int
nfields
;
int
lnr
;
char
*
fpr
=
NULL
;
uidinfo_list_t
mboxes
=
NULL
;
if
(
r_fpr
)
*
r_fpr
=
NULL
;
*
r_mboxes
=
NULL
;
/* Open a memory stream. */
listing
=
es_fopenmem
(
0
,
"w+b"
);
if
(
!
listing
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error allocating memory buffer: %s
\n
"
,
gpg_strerror
(
err
));
return
err
;
}
ccparray_init
(
&
ccp
,
0
);
ccparray_put
(
&
ccp
,
"--no-options"
);
if
(
!
opt
.
verbose
)
ccparray_put
(
&
ccp
,
"--quiet"
);
else
if
(
opt
.
verbose
>
1
)
ccparray_put
(
&
ccp
,
"--verbose"
);
ccparray_put
(
&
ccp
,
"--batch"
);
ccparray_put
(
&
ccp
,
"--status-fd=2"
);
ccparray_put
(
&
ccp
,
"--always-trust"
);
ccparray_put
(
&
ccp
,
"--with-colons"
);
ccparray_put
(
&
ccp
,
"--dry-run"
);
ccparray_put
(
&
ccp
,
"--import-options=import-minimal,import-show"
);
ccparray_put
(
&
ccp
,
"--import"
);
ccparray_put
(
&
ccp
,
NULL
);
argv
=
ccparray_get
(
&
ccp
,
NULL
);
if
(
!
argv
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
err
=
gnupg_exec_tool_stream
(
opt
.
gpg_program
,
argv
,
key
,
NULL
,
listing
,
key_status_cb
,
NULL
);
if
(
err
)
{
log_error
(
"import failed: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
es_rewind
(
listing
);
lnr
=
0
;
maxlen
=
2048
;
/* Set limit. */
while
((
len
=
es_read_line
(
listing
,
&
line
,
&
length_of_line
,
&
maxlen
))
>
0
)
{
lnr
++
;
if
(
!
maxlen
)
{
log_error
(
"received line too long
\n
"
);
err
=
gpg_error
(
GPG_ERR_LINE_TOO_LONG
);
goto
leave
;
}
/* Strip newline and carriage return, if present. */
while
(
len
>
0
&&
(
line
[
len
-
1
]
==
'\n'
||
line
[
len
-
1
]
==
'\r'
))
line
[
--
len
]
=
'\0'
;
/* log_debug ("line '%s'\n", line); */
xfree
(
fields
);
fields
=
strtokenize_nt
(
line
,
":"
);
if
(
!
fields
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"strtokenize failed: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
for
(
nfields
=
0
;
fields
[
nfields
];
nfields
++
)
;
if
(
!
nfields
)
{
err
=
gpg_error
(
GPG_ERR_INV_ENGINE
);
goto
leave
;
}
if
(
!
strcmp
(
fields
[
0
],
"sec"
))
{
/* gpg may return "sec" as the first record - but we do not
* accept secret keys. */
err
=
gpg_error
(
GPG_ERR_NO_PUBKEY
);
goto
leave
;
}
if
(
lnr
==
1
&&
strcmp
(
fields
[
0
],
"pub"
))
{
/* First record is not a public key. */
err
=
gpg_error
(
GPG_ERR_INV_ENGINE
);
goto
leave
;
}
if
(
lnr
>
1
&&
!
strcmp
(
fields
[
0
],
"pub"
))
{
/* More than one public key. */
err
=
gpg_error
(
GPG_ERR_TOO_MANY
);
goto
leave
;
}
if
(
!
strcmp
(
fields
[
0
],
"sub"
)
||
!
strcmp
(
fields
[
0
],
"ssb"
))
break
;
/* We can stop parsing here. */
if
(
!
strcmp
(
fields
[
0
],
"fpr"
)
&&
nfields
>
9
&&
!
fpr
)
{
fpr
=
xtrystrdup
(
fields
[
9
]);
if
(
!
fpr
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
}
else
if
(
!
strcmp
(
fields
[
0
],
"uid"
)
&&
nfields
>
9
)
{
if
(
!
append_to_uidinfo_list
(
&
mboxes
,
fields
[
9
],
parse_timestamp
(
fields
[
5
],
NULL
)))
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
}
}
if
(
len
<
0
||
es_ferror
(
listing
))
{
err
=
gpg_error_from_syserror
();
log_error
(
"error reading memory stream
\n
"
);
goto
leave
;
}
if
(
!
fpr
)
{
err
=
gpg_error
(
GPG_ERR_NO_PUBKEY
);
goto
leave
;
}
if
(
r_fpr
)
{
*
r_fpr
=
fpr
;
fpr
=
NULL
;
}
*
r_mboxes
=
mboxes
;
mboxes
=
NULL
;
leave
:
xfree
(
fpr
);
free_uidinfo_list
(
mboxes
);
xfree
(
fields
);
es_free
(
line
);
xfree
(
argv
);
es_fclose
(
listing
);
return
err
;
}
/* Run gpg as a filter on KEY and write the output to a new stream
* stored at R_NEWKEY. The new key will contain only the user id UID.
* Returns 0 on success. Only one key is expected in KEY. If BINARY
* is set the resulting key is returned as a binary (non-armored)
* keyblock. */
gpg_error_t
wks_filter_uid
(
estream_t
*
r_newkey
,
estream_t
key
,
const
char
*
uid
,
int
binary
)
{
gpg_error_t
err
;
ccparray_t
ccp
;
const
char
**
argv
=
NULL
;
estream_t
newkey
;
char
*
filterexp
=
NULL
;
*
r_newkey
=
NULL
;
/* Open a memory stream. */
newkey
=
es_fopenmem
(
0
,
"w+b"
);
if
(
!
newkey
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error allocating memory buffer: %s
\n
"
,
gpg_strerror
(
err
));
return
err
;
}
/* Prefix the key with the MIME content type. */
if
(
!
binary
)
es_fputs
(
"Content-Type: application/pgp-keys
\n
"
"
\n
"
,
newkey
);
filterexp
=
es_bsprintf
(
"keep-uid=-t uid= %s"
,
uid
);
if
(
!
filterexp
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error allocating memory buffer: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
ccparray_init
(
&
ccp
,
0
);
ccparray_put
(
&
ccp
,
"--no-options"
);
if
(
!
opt
.
verbose
)
ccparray_put
(
&
ccp
,
"--quiet"
);
else
if
(
opt
.
verbose
>
1
)
ccparray_put
(
&
ccp
,
"--verbose"
);
ccparray_put
(
&
ccp
,
"--batch"
);
ccparray_put
(
&
ccp
,
"--status-fd=2"
);
ccparray_put
(
&
ccp
,
"--always-trust"
);
if
(
!
binary
)
ccparray_put
(
&
ccp
,
"--armor"
);
ccparray_put
(
&
ccp
,
"--import-options=import-export"
);
ccparray_put
(
&
ccp
,
"--import-filter"
);
ccparray_put
(
&
ccp
,
filterexp
);
ccparray_put
(
&
ccp
,
"--import"
);
ccparray_put
(
&
ccp
,
NULL
);
argv
=
ccparray_get
(
&
ccp
,
NULL
);
if
(
!
argv
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
err
=
gnupg_exec_tool_stream
(
opt
.
gpg_program
,
argv
,
key
,
NULL
,
newkey
,
key_status_cb
,
NULL
);
if
(
err
)
{
log_error
(
"import/export failed: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
es_rewind
(
newkey
);
*
r_newkey
=
newkey
;
newkey
=
NULL
;
leave
:
xfree
(
filterexp
);
xfree
(
argv
);
es_fclose
(
newkey
);
return
err
;
}
/* Helper to write mail to the output(s). */
gpg_error_t
wks_send_mime
(
mime_maker_t
mime
)
{
gpg_error_t
err
;
estream_t
mail
;
/* Without any option we take a short path. */
if
(
!
opt
.
use_sendmail
&&
!
opt
.
output
)
{
es_set_binary
(
es_stdout
);
return
mime_maker_make
(
mime
,
es_stdout
);
}
mail
=
es_fopenmem
(
0
,
"w+b"
);
if
(
!
mail
)
{
err
=
gpg_error_from_syserror
();
return
err
;
}
err
=
mime_maker_make
(
mime
,
mail
);
if
(
!
err
&&
opt
.
output
)
{
es_rewind
(
mail
);
err
=
send_mail_to_file
(
mail
,
opt
.
output
);
}
if
(
!
err
&&
opt
.
use_sendmail
)
{
es_rewind
(
mail
);
err
=
send_mail
(
mail
);
}
es_fclose
(
mail
);
return
err
;
}
/* Parse the policy flags by reading them from STREAM and storing them
* into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are
* ignored. */
gpg_error_t
wks_parse_policy
(
policy_flags_t
flags
,
estream_t
stream
,
int
ignore_unknown
)
{
enum
tokens
{
TOK_SUBMISSION_ADDRESS
,
TOK_MAILBOX_ONLY
,
TOK_DANE_ONLY
,
TOK_AUTH_SUBMIT
,
TOK_MAX_PENDING
,
TOK_PROTOCOL_VERSION
};
static
struct
{
const
char
*
name
;
enum
tokens
token
;
}
keywords
[]
=
{
{
"submission-address"
,
TOK_SUBMISSION_ADDRESS
},
{
"mailbox-only"
,
TOK_MAILBOX_ONLY
},
{
"dane-only"
,
TOK_DANE_ONLY
},
{
"auth-submit"
,
TOK_AUTH_SUBMIT
},
{
"max-pending"
,
TOK_MAX_PENDING
},
{
"protocol-version"
,
TOK_PROTOCOL_VERSION
}
};
gpg_error_t
err
=
0
;
int
lnr
=
0
;
char
line
[
1024
];
char
*
p
,
*
keyword
,
*
value
;
int
i
,
n
;
memset
(
flags
,
0
,
sizeof
*
flags
);
while
(
es_fgets
(
line
,
DIM
(
line
)
-1
,
stream
)
)
{
lnr
++
;
n
=
strlen
(
line
);
if
(
!
n
||
line
[
n
-1
]
!=
'\n'
)
{
err
=
gpg_error
(
*
line
?
GPG_ERR_LINE_TOO_LONG
:
GPG_ERR_INCOMPLETE_LINE
);
break
;
}
trim_trailing_spaces
(
line
);
/* Skip empty and comment lines. */
for
(
p
=
line
;
spacep
(
p
);
p
++
)
;
if
(
!*
p
||
*
p
==
'#'
)
continue
;
if
(
*
p
==
':'
)
{
err
=
gpg_error
(
GPG_ERR_SYNTAX
);
break
;
}
keyword
=
p
;
value
=
NULL
;
if
((
p
=
strchr
(
p
,
':'
)))
{
/* Colon found: Keyword with value. */
*
p
++
=
0
;
for
(;
spacep
(
p
);
p
++
)
;
if
(
!*
p
)
{
err
=
gpg_error
(
GPG_ERR_MISSING_VALUE
);
break
;
}
value
=
p
;
}
for
(
i
=
0
;
i
<
DIM
(
keywords
);
i
++
)
if
(
!
ascii_strcasecmp
(
keywords
[
i
].
name
,
keyword
))
break
;
if
(
!
(
i
<
DIM
(
keywords
)))
{
if
(
ignore_unknown
)
continue
;
err
=
gpg_error
(
GPG_ERR_INV_NAME
);
break
;
}
switch
(
keywords
[
i
].
token
)
{
case
TOK_SUBMISSION_ADDRESS
:
if
(
!
value
||
!*
value
)
{
err
=
gpg_error
(
GPG_ERR_SYNTAX
);
goto
leave
;
}
xfree
(
flags
->
submission_address
);
flags
->
submission_address
=
xtrystrdup
(
value
);
if
(
!
flags
->
submission_address
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
break
;
case
TOK_MAILBOX_ONLY
:
flags
->
mailbox_only
=
1
;
break
;
case
TOK_DANE_ONLY
:
flags
->
dane_only
=
1
;
break
;
case
TOK_AUTH_SUBMIT
:
flags
->
auth_submit
=
1
;
break
;
case
TOK_MAX_PENDING
:
if
(
!
value
)
{
err
=
gpg_error
(
GPG_ERR_SYNTAX
);
goto
leave
;
}
/* FIXME: Define whether these are seconds, hours, or days
* and decide whether to allow other units. */
flags
->
max_pending
=
atoi
(
value
);
break
;
case
TOK_PROTOCOL_VERSION
:
if
(
!
value
)
{
err
=
gpg_error
(
GPG_ERR_SYNTAX
);
goto
leave
;
}
flags
->
protocol_version
=
atoi
(
value
);
break
;
}
}
if
(
!
err
&&
!
es_feof
(
stream
))
err
=
gpg_error_from_syserror
();
leave
:
if
(
err
)
log_error
(
"error reading '%s', line %d: %s
\n
"
,
es_fname_get
(
stream
),
lnr
,
gpg_strerror
(
err
));
return
err
;
}
void
wks_free_policy
(
policy_flags_t
policy
)
{
if
(
policy
)
{
xfree
(
policy
->
submission_address
);
memset
(
policy
,
0
,
sizeof
*
policy
);
}
}
/* Write the content of SRC to the new file FNAME. */
static
gpg_error_t
write_to_file
(
estream_t
src
,
const
char
*
fname
)
{
gpg_error_t
err
;
estream_t
dst
;
char
buffer
[
4096
];
size_t
nread
,
written
;
dst
=
es_fopen
(
fname
,
"wb"
);
if
(
!
dst
)
return
gpg_error_from_syserror
();
do
{
nread
=
es_fread
(
buffer
,
1
,
sizeof
buffer
,
src
);
if
(
!
nread
)
break
;
written
=
es_fwrite
(
buffer
,
1
,
nread
,
dst
);
if
(
written
!=
nread
)
break
;
}
while
(
!
es_feof
(
src
)
&&
!
es_ferror
(
src
)
&&
!
es_ferror
(
dst
));
if
(
!
es_feof
(
src
)
||
es_ferror
(
src
)
||
es_ferror
(
dst
))
{
err
=
gpg_error_from_syserror
();
es_fclose
(
dst
);
gnupg_remove
(
fname
);
return
err
;
}
if
(
es_fclose
(
dst
))
{
err
=
gpg_error_from_syserror
();
log_error
(
"error closing '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
return
err
;
}
return
0
;
}
/* Return the filename and optionally the addrspec for USERID at
* R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If
* HASH_ONLY is set only the has is returned at R_FNAME and no file is
* created. */
gpg_error_t
wks_fname_from_userid
(
const
char
*
userid
,
int
hash_only
,
char
**
r_fname
,
char
**
r_addrspec
)
{
gpg_error_t
err
;
char
*
addrspec
=
NULL
;
const
char
*
domain
;
char
*
hash
=
NULL
;
const
char
*
s
;
char
shaxbuf
[
32
];
/* Used for SHA-1 and SHA-256 */
*
r_fname
=
NULL
;
if
(
r_addrspec
)
*
r_addrspec
=
NULL
;
addrspec
=
mailbox_from_userid
(
userid
,
0
);
if
(
!
addrspec
)
{
if
(
opt
.
verbose
||
hash_only
)
log_info
(
"
\"
%s
\"
is not a proper mail address
\n
"
,
userid
);
err
=
gpg_error
(
GPG_ERR_INV_USER_ID
);
goto
leave
;
}
domain
=
strchr
(
addrspec
,
'@'
);
log_assert
(
domain
);
domain
++
;
if
(
strchr
(
domain
,
'/'
)
||
strchr
(
domain
,
'\\'
))
{
log_info
(
"invalid domain detected ('%s')
\n
"
,
domain
);
err
=
gpg_error
(
GPG_ERR_NOT_FOUND
);
goto
leave
;
}
/* Hash user ID and create filename. */
s
=
strchr
(
addrspec
,
'@'
);
log_assert
(
s
);
gcry_md_hash_buffer
(
GCRY_MD_SHA1
,
shaxbuf
,
addrspec
,
s
-
addrspec
);
hash
=
zb32_encode
(
shaxbuf
,
8
*
20
);
if
(
!
hash
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
if
(
hash_only
)
{
*
r_fname
=
hash
;
hash
=
NULL
;
err
=
0
;
}
else
{
*
r_fname
=
make_filename_try
(
opt
.
directory
,
domain
,
"hu"
,
hash
,
NULL
);
if
(
!*
r_fname
)
err
=
gpg_error_from_syserror
();
else
err
=
0
;
}
leave
:
if
(
r_addrspec
&&
addrspec
)
*
r_addrspec
=
addrspec
;
else
xfree
(
addrspec
);
xfree
(
hash
);
return
err
;
}
/* Compute the the full file name for the key with ADDRSPEC and return
* it at R_FNAME. */
gpg_error_t
wks_compute_hu_fname
(
char
**
r_fname
,
const
char
*
addrspec
)
{
gpg_error_t
err
;
char
*
hash
;
const
char
*
domain
;
char
sha1buf
[
20
];
char
*
fname
;
struct
stat
sb
;
*
r_fname
=
NULL
;
domain
=
strchr
(
addrspec
,
'@'
);
if
(
!
domain
||
!
domain
[
1
]
||
domain
==
addrspec
)
return
gpg_error
(
GPG_ERR_INV_ARG
);
domain
++
;
if
(
strchr
(
domain
,
'/'
)
||
strchr
(
domain
,
'\\'
))
{
log_info
(
"invalid domain detected ('%s')
\n
"
,
domain
);
return
gpg_error
(
GPG_ERR_NOT_FOUND
);
}
gcry_md_hash_buffer
(
GCRY_MD_SHA1
,
sha1buf
,
addrspec
,
domain
-
addrspec
-
1
);
hash
=
zb32_encode
(
sha1buf
,
8
*
20
);
if
(
!
hash
)
return
gpg_error_from_syserror
();
/* Try to create missing directories below opt.directory. */
fname
=
make_filename_try
(
opt
.
directory
,
domain
,
NULL
);
if
(
fname
&&
gnupg_stat
(
fname
,
&
sb
)
&&
gpg_err_code_from_syserror
()
==
GPG_ERR_ENOENT
)
if
(
!
gnupg_mkdir
(
fname
,
"-rwxr-xr-x"
)
&&
opt
.
verbose
)
log_info
(
"directory '%s' created
\n
"
,
fname
);
xfree
(
fname
);
fname
=
make_filename_try
(
opt
.
directory
,
domain
,
"hu"
,
NULL
);
if
(
fname
&&
gnupg_stat
(
fname
,
&
sb
)
&&
gpg_err_code_from_syserror
()
==
GPG_ERR_ENOENT
)
if
(
!
gnupg_mkdir
(
fname
,
"-rwxr-xr-x"
)
&&
opt
.
verbose
)
log_info
(
"directory '%s' created
\n
"
,
fname
);
xfree
(
fname
);
/* Create the filename. */
fname
=
make_filename_try
(
opt
.
directory
,
domain
,
"hu"
,
hash
,
NULL
);
err
=
fname
?
0
:
gpg_error_from_syserror
();
if
(
err
)
xfree
(
fname
);
else
*
r_fname
=
fname
;
/* Okay. */
xfree
(
hash
);
return
err
;
}
/* Make sure that a policy file exists for addrspec. Directories must
* already exist. */
static
gpg_error_t
ensure_policy_file
(
const
char
*
addrspec
)
{
gpg_err_code_t
ec
;
gpg_error_t
err
;
const
char
*
domain
;
char
*
fname
;
estream_t
fp
;
domain
=
strchr
(
addrspec
,
'@'
);
if
(
!
domain
||
!
domain
[
1
]
||
domain
==
addrspec
)
return
gpg_error
(
GPG_ERR_INV_ARG
);
domain
++
;
if
(
strchr
(
domain
,
'/'
)
||
strchr
(
domain
,
'\\'
))
{
log_info
(
"invalid domain detected ('%s')
\n
"
,
domain
);
return
gpg_error
(
GPG_ERR_NOT_FOUND
);
}
/* Create the filename. */
fname
=
make_filename_try
(
opt
.
directory
,
domain
,
"policy"
,
NULL
);
err
=
fname
?
0
:
gpg_error_from_syserror
();
if
(
err
)
goto
leave
;
/* First a quick check whether it already exists. */
if
(
!
(
ec
=
gnupg_access
(
fname
,
F_OK
)))
{
err
=
0
;
/* File already exists. */
goto
leave
;
}
err
=
gpg_error
(
ec
);
if
(
gpg_err_code
(
err
)
==
GPG_ERR_ENOENT
)
err
=
0
;
else
{
log_error
(
"domain %s: problem with '%s': %s
\n
"
,
domain
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
/* Now create the file. */
fp
=
es_fopen
(
fname
,
"wxb"
);
if
(
!
fp
)
{
err
=
gpg_error_from_syserror
();
if
(
gpg_err_code
(
err
)
==
GPG_ERR_EEXIST
)
err
=
0
;
/* Was created between the gnupg_access() and es_fopen(). */
else
log_error
(
"domain %s: error creating '%s': %s
\n
"
,
domain
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
es_fprintf
(
fp
,
"# Policy flags for domain %s
\n
"
,
domain
);
if
(
es_ferror
(
fp
)
||
es_fclose
(
fp
))
{
err
=
gpg_error_from_syserror
();
log_error
(
"error writing '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
if
(
opt
.
verbose
)
log_info
(
"policy file '%s' created
\n
"
,
fname
);
/* Make sure the policy file world readable. */
if
(
gnupg_chmod
(
fname
,
"-rw-r--r--"
))
{
err
=
gpg_error_from_syserror
();
log_error
(
"can't set permissions of '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
leave
:
xfree
(
fname
);
return
err
;
}
/* Helper form wks_cmd_install_key. */
static
gpg_error_t
install_key_from_spec_file
(
const
char
*
fname
)
{
gpg_error_t
err
;
estream_t
fp
;
char
*
line
=
NULL
;
size_t
linelen
=
0
;
size_t
maxlen
=
2048
;
const
char
*
fields
[
2
];
unsigned
int
lnr
=
0
;
if
(
!
fname
||
!
strcmp
(
fname
,
""
))
fp
=
es_stdin
;
else
fp
=
es_fopen
(
fname
,
"rb"
);
if
(
!
fp
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error reading '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
while
(
es_read_line
(
fp
,
&
line
,
&
linelen
,
&
maxlen
)
>
0
)
{
if
(
!
maxlen
)
{
err
=
gpg_error
(
GPG_ERR_LINE_TOO_LONG
);
log_error
(
"error reading '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
lnr
++
;
trim_spaces
(
line
);
if
(
!*
line
||
*
line
==
'#'
)
continue
;
if
(
split_fields
(
line
,
fields
,
DIM
(
fields
))
<
2
)
{
log_error
(
"error reading '%s': syntax error at line %u
\n
"
,
fname
,
lnr
);
continue
;
}
err
=
wks_cmd_install_key
(
fields
[
0
],
fields
[
1
]);
if
(
err
)
goto
leave
;
}
if
(
es_ferror
(
fp
))
{
err
=
gpg_error_from_syserror
();
log_error
(
"error reading '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
leave
:
if
(
fp
!=
es_stdin
)
es_fclose
(
fp
);
es_free
(
line
);
return
err
;
}
/* Install a single key into the WKD by reading FNAME and extracting
* USERID. If USERID is NULL FNAME is expected to be a list of fpr
* mbox lines and for each line the respective key will be
* installed. */
gpg_error_t
wks_cmd_install_key
(
const
char
*
fname
,
const
char
*
userid
)
{
gpg_error_t
err
;
KEYDB_SEARCH_DESC
desc
;
estream_t
fp
=
NULL
;
char
*
addrspec
=
NULL
;
char
*
fpr
=
NULL
;
uidinfo_list_t
uidlist
=
NULL
;
uidinfo_list_t
uid
,
thisuid
;
time_t
thistime
;
char
*
huname
=
NULL
;
int
any
;
if
(
!
userid
)
return
install_key_from_spec_file
(
fname
);
addrspec
=
mailbox_from_userid
(
userid
,
0
);
if
(
!
addrspec
)
{
log_error
(
"
\"
%s
\"
is not a proper mail address
\n
"
,
userid
);
err
=
gpg_error
(
GPG_ERR_INV_USER_ID
);
goto
leave
;
}
if
(
!
classify_user_id
(
fname
,
&
desc
,
1
)
&&
desc
.
mode
==
KEYDB_SEARCH_MODE_FPR
)
{
/* FNAME looks like a fingerprint. Get the key from the
* standard keyring. */
err
=
wks_get_key
(
&
fp
,
fname
,
addrspec
,
0
);
if
(
err
)
{
log_error
(
"error getting key '%s' (uid='%s'): %s
\n
"
,
fname
,
addrspec
,
gpg_strerror
(
err
));
goto
leave
;
}
}
else
/* Take it from the file */
{
fp
=
es_fopen
(
fname
,
"rb"
);
if
(
!
fp
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error reading '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
}
/* List the key so that we can figure out the newest UID with the
* requested addrspec. */
err
=
wks_list_key
(
fp
,
&
fpr
,
&
uidlist
);
if
(
err
)
{
log_error
(
"error parsing key: %s
\n
"
,
gpg_strerror
(
err
));
err
=
gpg_error
(
GPG_ERR_NO_PUBKEY
);
goto
leave
;
}
thistime
=
0
;
thisuid
=
NULL
;
any
=
0
;
for
(
uid
=
uidlist
;
uid
;
uid
=
uid
->
next
)
{
if
(
!
uid
->
mbox
)
continue
;
/* Should not happen anyway. */
if
(
ascii_strcasecmp
(
uid
->
mbox
,
addrspec
))
continue
;
/* Not the requested addrspec. */
any
=
1
;
if
(
uid
->
created
>
thistime
)
{
thistime
=
uid
->
created
;
thisuid
=
uid
;
}
}
if
(
!
thisuid
)
thisuid
=
uidlist
;
/* This is the case for a missing timestamp. */
if
(
!
any
)
{
log_error
(
"public key in '%s' has no mail address '%s'
\n
"
,
fname
,
addrspec
);
err
=
gpg_error
(
GPG_ERR_INV_USER_ID
);
goto
leave
;
}
if
(
opt
.
verbose
)
log_info
(
"using key with user id '%s'
\n
"
,
thisuid
->
uid
);
{
estream_t
fp2
;
es_rewind
(
fp
);
err
=
wks_filter_uid
(
&
fp2
,
fp
,
thisuid
->
uid
,
1
);
if
(
err
)
{
log_error
(
"error filtering key: %s
\n
"
,
gpg_strerror
(
err
));
err
=
gpg_error
(
GPG_ERR_NO_PUBKEY
);
goto
leave
;
}
es_fclose
(
fp
);
fp
=
fp2
;
}
/* Hash user ID and create filename. */
err
=
wks_compute_hu_fname
(
&
huname
,
addrspec
);
if
(
err
)
goto
leave
;
/* Now that wks_compute_hu_fname has created missing directories we
* can create a policy file if it does not exist. */
err
=
ensure_policy_file
(
addrspec
);
if
(
err
)
goto
leave
;
/* Publish. */
err
=
write_to_file
(
fp
,
huname
);
if
(
err
)
{
log_error
(
"copying key to '%s' failed: %s
\n
"
,
huname
,
gpg_strerror
(
err
));
goto
leave
;
}
/* Make sure it is world readable. */
if
(
gnupg_chmod
(
huname
,
"-rw-r--r--"
))
log_error
(
"can't set permissions of '%s': %s
\n
"
,
huname
,
gpg_strerror
(
gpg_err_code_from_syserror
()));
if
(
!
opt
.
quiet
)
log_info
(
"key %s published for '%s'
\n
"
,
fpr
,
addrspec
);
leave
:
xfree
(
huname
);
free_uidinfo_list
(
uidlist
);
xfree
(
fpr
);
xfree
(
addrspec
);
es_fclose
(
fp
);
return
err
;
}
/* Remove the key with mail address in USERID. */
gpg_error_t
wks_cmd_remove_key
(
const
char
*
userid
)
{
gpg_error_t
err
;
char
*
addrspec
=
NULL
;
char
*
fname
=
NULL
;
err
=
wks_fname_from_userid
(
userid
,
0
,
&
fname
,
&
addrspec
);
if
(
err
)
goto
leave
;
if
(
gnupg_remove
(
fname
))
{
err
=
gpg_error_from_syserror
();
if
(
gpg_err_code
(
err
)
==
GPG_ERR_ENOENT
)
{
if
(
!
opt
.
quiet
)
log_info
(
"key for '%s' is not installed
\n
"
,
addrspec
);
log_inc_errorcount
();
err
=
0
;
}
else
log_error
(
"error removing '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
if
(
opt
.
verbose
)
log_info
(
"key for '%s' removed
\n
"
,
addrspec
);
err
=
0
;
leave
:
xfree
(
fname
);
xfree
(
addrspec
);
return
err
;
}
/* Print the WKD hash for the user id to stdout. */
gpg_error_t
wks_cmd_print_wkd_hash
(
const
char
*
userid
)
{
gpg_error_t
err
;
char
*
addrspec
,
*
fname
;
err
=
wks_fname_from_userid
(
userid
,
1
,
&
fname
,
&
addrspec
);
if
(
err
)
return
err
;
es_printf
(
"%s %s
\n
"
,
fname
,
addrspec
);
xfree
(
fname
);
xfree
(
addrspec
);
return
err
;
}
/* Print the WKD URL for the user id to stdout. */
gpg_error_t
wks_cmd_print_wkd_url
(
const
char
*
userid
)
{
gpg_error_t
err
;
char
*
addrspec
,
*
fname
;
char
*
domain
;
err
=
wks_fname_from_userid
(
userid
,
1
,
&
fname
,
&
addrspec
);
if
(
err
)
return
err
;
domain
=
strchr
(
addrspec
,
'@'
);
if
(
domain
)
*
domain
++
=
0
;
es_printf
(
"https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s
\n
"
,
domain
,
domain
,
fname
,
addrspec
);
xfree
(
fname
);
xfree
(
addrspec
);
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Fri, Dec 5, 5:36 AM (1 d, 15 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
88/f3/1844e98f911dc84fedb096cfd133
Attached To
rG GnuPG
Event Timeline
Log In to Comment