Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F22947668
name-value.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
17 KB
Subscribers
None
name-value.c
View Options
/* name-value.c - Parser and writer for a name-value format.
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
* This module aso provides features for the extended private key
* format of gpg-agent.
*/
#include
<config.h>
#include
<assert.h>
#include
<gcrypt.h>
#include
<gpg-error.h>
#include
<string.h>
#include
"mischelp.h"
#include
"strlist.h"
#include
"util.h"
#include
"name-value.h"
struct
name_value_container
{
struct
name_value_entry
*
first
;
struct
name_value_entry
*
last
;
unsigned
int
private_key_mode
:
1
;
};
struct
name_value_entry
{
struct
name_value_entry
*
prev
;
struct
name_value_entry
*
next
;
/* The name. Comments and blank lines have NAME set to NULL. */
char
*
name
;
/* The value as stored in the file. We store it when we parse
a file so that we can reproduce it. */
strlist_t
raw_value
;
/* The decoded value. */
char
*
value
;
};
/* Helper */
static
inline
gpg_error_t
my_error_from_syserror
(
void
)
{
return
gpg_err_make
(
default_errsource
,
gpg_err_code_from_syserror
());
}
static
inline
gpg_error_t
my_error
(
gpg_err_code_t
ec
)
{
return
gpg_err_make
(
default_errsource
,
ec
);
}
/* Allocation and deallocation. */
/* Allocate a private key container structure. */
nvc_t
nvc_new
(
void
)
{
return
xtrycalloc
(
1
,
sizeof
(
struct
name_value_container
));
}
/* Allocate a private key container structure for use with private keys. */
nvc_t
nvc_new_private_key
(
void
)
{
nvc_t
nvc
=
nvc_new
();
if
(
nvc
)
nvc
->
private_key_mode
=
1
;
return
nvc
;
}
static
void
nve_release
(
nve_t
entry
,
int
private_key_mode
)
{
if
(
entry
==
NULL
)
return
;
xfree
(
entry
->
name
);
if
(
entry
->
value
&&
private_key_mode
)
wipememory
(
entry
->
value
,
strlen
(
entry
->
value
));
xfree
(
entry
->
value
);
if
(
private_key_mode
)
free_strlist_wipe
(
entry
->
raw_value
);
else
free_strlist
(
entry
->
raw_value
);
xfree
(
entry
);
}
/* Release a private key container structure. */
void
nvc_release
(
nvc_t
pk
)
{
nve_t
e
,
next
;
if
(
pk
==
NULL
)
return
;
for
(
e
=
pk
->
first
;
e
;
e
=
next
)
{
next
=
e
->
next
;
nve_release
(
e
,
pk
->
private_key_mode
);
}
xfree
(
pk
);
}
/* Dealing with names and values. */
/* Check whether the given name is valid. Valid names start with a
letter, end with a colon, and contain only alphanumeric characters
and the hyphen. */
static
int
valid_name
(
const
char
*
name
)
{
size_t
i
,
len
=
strlen
(
name
);
if
(
!
alphap
(
name
)
||
len
==
0
||
name
[
len
-
1
]
!=
':'
)
return
0
;
for
(
i
=
1
;
i
<
len
-
1
;
i
++
)
if
(
!
alnump
(
&
name
[
i
])
&&
name
[
i
]
!=
'-'
)
return
0
;
return
1
;
}
/* Makes sure that ENTRY has a RAW_VALUE. */
static
gpg_error_t
assert_raw_value
(
nve_t
entry
)
{
gpg_error_t
err
=
0
;
size_t
len
,
offset
;
#define LINELEN 70
char
buf
[
LINELEN
+
3
];
if
(
entry
->
raw_value
)
return
0
;
len
=
strlen
(
entry
->
value
);
offset
=
0
;
while
(
len
)
{
size_t
amount
,
linelen
=
LINELEN
;
/* On the first line we need to subtract space for the name. */
if
(
entry
->
raw_value
==
NULL
&&
strlen
(
entry
->
name
)
<
linelen
)
linelen
-=
strlen
(
entry
->
name
);
/* See if the rest of the value fits in this line. */
if
(
len
<=
linelen
)
amount
=
len
;
else
{
size_t
i
;
/* Find a suitable space to break on. */
for
(
i
=
linelen
-
1
;
linelen
-
i
<
30
;
i
--
)
if
(
ascii_isspace
(
entry
->
value
[
offset
+
i
]))
break
;
if
(
ascii_isspace
(
entry
->
value
[
offset
+
i
]))
{
/* Found one. */
amount
=
i
;
}
else
{
/* Just induce a hard break. */
amount
=
linelen
;
}
}
snprintf
(
buf
,
sizeof
buf
,
" %.*s
\n
"
,
(
int
)
amount
,
&
entry
->
value
[
offset
]);
if
(
append_to_strlist_try
(
&
entry
->
raw_value
,
buf
)
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
offset
+=
amount
;
len
-=
amount
;
}
leave
:
if
(
err
)
{
free_strlist_wipe
(
entry
->
raw_value
);
entry
->
raw_value
=
NULL
;
}
return
err
;
#undef LINELEN
}
/* Computes the length of the value encoded as continuation. If
*SWALLOW_WS is set, all whitespace at the beginning of S is
swallowed. If START is given, a pointer to the beginning of the
value is stored there. */
static
size_t
continuation_length
(
const
char
*
s
,
int
*
swallow_ws
,
const
char
**
start
)
{
size_t
len
;
if
(
*
swallow_ws
)
{
/* The previous line was a blank line and we inserted a newline.
Swallow all whitespace at the beginning of this line. */
while
(
ascii_isspace
(
*
s
))
s
++
;
}
else
{
/* Iff a continuation starts with more than one space, it
encodes a space. */
if
(
ascii_isspace
(
*
s
))
s
++
;
}
/* Strip whitespace at the end. */
len
=
strlen
(
s
);
while
(
len
>
0
&&
ascii_isspace
(
s
[
len
-1
]))
len
--
;
if
(
len
==
0
)
{
/* Blank lines encode newlines. */
len
=
1
;
s
=
"
\n
"
;
*
swallow_ws
=
1
;
}
else
*
swallow_ws
=
0
;
if
(
start
)
*
start
=
s
;
return
len
;
}
/* Makes sure that ENTRY has a VALUE. */
static
gpg_error_t
assert_value
(
nve_t
entry
)
{
size_t
len
;
int
swallow_ws
;
strlist_t
s
;
char
*
p
;
if
(
entry
->
value
)
return
0
;
len
=
0
;
swallow_ws
=
0
;
for
(
s
=
entry
->
raw_value
;
s
;
s
=
s
->
next
)
len
+=
continuation_length
(
s
->
d
,
&
swallow_ws
,
NULL
);
/* Add one for the terminating zero. */
len
+=
1
;
entry
->
value
=
p
=
xtrymalloc
(
len
);
if
(
entry
->
value
==
NULL
)
return
my_error_from_syserror
();
swallow_ws
=
0
;
for
(
s
=
entry
->
raw_value
;
s
;
s
=
s
->
next
)
{
const
char
*
start
;
size_t
l
=
continuation_length
(
s
->
d
,
&
swallow_ws
,
&
start
);
memcpy
(
p
,
start
,
l
);
p
+=
l
;
}
*
p
++
=
0
;
assert
(
p
-
entry
->
value
==
len
);
return
0
;
}
/* Get the name. */
char
*
nve_name
(
nve_t
pke
)
{
return
pke
->
name
;
}
/* Get the value. */
char
*
nve_value
(
nve_t
pke
)
{
if
(
assert_value
(
pke
))
return
NULL
;
return
pke
->
value
;
}
/* Adding and modifying values. */
/* Add (NAME, VALUE, RAW_VALUE) to PK. NAME may be NULL for comments
and blank lines. At least one of VALUE and RAW_VALUE must be
given. If PRESERVE_ORDER is not given, entries with the same name
are grouped. NAME, VALUE and RAW_VALUE is consumed. */
static
gpg_error_t
_nvc_add
(
nvc_t
pk
,
char
*
name
,
char
*
value
,
strlist_t
raw_value
,
int
preserve_order
)
{
gpg_error_t
err
=
0
;
nve_t
e
;
assert
(
value
||
raw_value
);
if
(
name
&&
!
valid_name
(
name
))
{
err
=
my_error
(
GPG_ERR_INV_NAME
);
goto
leave
;
}
if
(
name
&&
pk
->
private_key_mode
&&
!
ascii_strcasecmp
(
name
,
"Key:"
)
&&
nvc_lookup
(
pk
,
"Key:"
))
{
err
=
my_error
(
GPG_ERR_INV_NAME
);
goto
leave
;
}
e
=
xtrycalloc
(
1
,
sizeof
*
e
);
if
(
e
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
e
->
name
=
name
;
e
->
value
=
value
;
e
->
raw_value
=
raw_value
;
if
(
pk
->
first
)
{
nve_t
last
;
if
(
preserve_order
||
name
==
NULL
)
last
=
pk
->
last
;
else
{
/* See if there is already an entry with NAME. */
last
=
nvc_lookup
(
pk
,
name
);
/* If so, find the last in that block. */
if
(
last
)
{
while
(
last
->
next
)
{
nve_t
next
=
last
->
next
;
if
(
next
->
name
&&
ascii_strcasecmp
(
next
->
name
,
name
)
==
0
)
last
=
next
;
else
break
;
}
}
else
/* Otherwise, just find the last entry. */
last
=
pk
->
last
;
}
if
(
last
->
next
)
{
e
->
prev
=
last
;
e
->
next
=
last
->
next
;
last
->
next
=
e
;
e
->
next
->
prev
=
e
;
}
else
{
e
->
prev
=
last
;
last
->
next
=
e
;
pk
->
last
=
e
;
}
}
else
pk
->
first
=
pk
->
last
=
e
;
leave
:
if
(
err
)
{
xfree
(
name
);
if
(
value
)
wipememory
(
value
,
strlen
(
value
));
xfree
(
value
);
free_strlist_wipe
(
raw_value
);
}
return
err
;
}
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is not updated but the new entry is appended. */
gpg_error_t
nvc_add
(
nvc_t
pk
,
const
char
*
name
,
const
char
*
value
)
{
char
*
k
,
*
v
;
k
=
xtrystrdup
(
name
);
if
(
k
==
NULL
)
return
my_error_from_syserror
();
v
=
xtrystrdup
(
value
);
if
(
v
==
NULL
)
{
xfree
(
k
);
return
my_error_from_syserror
();
}
return
_nvc_add
(
pk
,
k
,
v
,
NULL
,
0
);
}
/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
is updated with VALUE. If multiple entries with NAME exist, the
first entry is updated. */
gpg_error_t
nvc_set
(
nvc_t
pk
,
const
char
*
name
,
const
char
*
value
)
{
nve_t
e
;
if
(
!
valid_name
(
name
))
return
GPG_ERR_INV_NAME
;
e
=
nvc_lookup
(
pk
,
name
);
if
(
e
)
{
char
*
v
;
v
=
xtrystrdup
(
value
);
if
(
v
==
NULL
)
return
my_error_from_syserror
();
free_strlist_wipe
(
e
->
raw_value
);
e
->
raw_value
=
NULL
;
if
(
e
->
value
)
wipememory
(
e
->
value
,
strlen
(
e
->
value
));
xfree
(
e
->
value
);
e
->
value
=
v
;
return
0
;
}
else
return
nvc_add
(
pk
,
name
,
value
);
}
/* Delete the given entry from PK. */
void
nvc_delete
(
nvc_t
pk
,
nve_t
entry
)
{
if
(
entry
->
prev
)
entry
->
prev
->
next
=
entry
->
next
;
else
pk
->
first
=
entry
->
next
;
if
(
entry
->
next
)
entry
->
next
->
prev
=
entry
->
prev
;
else
pk
->
last
=
entry
->
prev
;
nve_release
(
entry
,
pk
->
private_key_mode
);
}
/* Delete the entries with NAME from PK. */
void
nvc_delete_named
(
nvc_t
pk
,
const
char
*
name
)
{
nve_t
e
;
if
(
!
valid_name
(
name
))
return
;
while
((
e
=
nvc_lookup
(
pk
,
name
)))
nvc_delete
(
pk
,
e
);
}
/* Lookup and iteration. */
/* Get the first non-comment entry. */
nve_t
nvc_first
(
nvc_t
pk
)
{
nve_t
entry
;
for
(
entry
=
pk
->
first
;
entry
;
entry
=
entry
->
next
)
if
(
entry
->
name
)
return
entry
;
return
NULL
;
}
/* Get the first entry with the given name. */
nve_t
nvc_lookup
(
nvc_t
pk
,
const
char
*
name
)
{
nve_t
entry
;
for
(
entry
=
pk
->
first
;
entry
;
entry
=
entry
->
next
)
if
(
entry
->
name
&&
ascii_strcasecmp
(
entry
->
name
,
name
)
==
0
)
return
entry
;
return
NULL
;
}
/* Get the next non-comment entry. */
nve_t
nve_next
(
nve_t
entry
)
{
for
(
entry
=
entry
->
next
;
entry
;
entry
=
entry
->
next
)
if
(
entry
->
name
)
return
entry
;
return
NULL
;
}
/* Get the next entry with the given name. */
nve_t
nve_next_value
(
nve_t
entry
,
const
char
*
name
)
{
for
(
entry
=
entry
->
next
;
entry
;
entry
=
entry
->
next
)
if
(
entry
->
name
&&
ascii_strcasecmp
(
entry
->
name
,
name
)
==
0
)
return
entry
;
return
NULL
;
}
/* Return the string for the first entry in NVC with NAME. If an
* entry with NAME is missing in NVC or its value is the empty string
* NULL is returned. Note that the The returned string is a pointer
* into NVC. */
const
char
*
nvc_get_string
(
nvc_t
nvc
,
const
char
*
name
)
{
nve_t
item
;
if
(
!
nvc
)
return
NULL
;
item
=
nvc_lookup
(
nvc
,
name
);
if
(
!
item
)
return
NULL
;
return
nve_value
(
item
);
}
/* Private key handling. */
/* Get the private key. */
gpg_error_t
nvc_get_private_key
(
nvc_t
pk
,
gcry_sexp_t
*
retsexp
)
{
gpg_error_t
err
;
nve_t
e
;
e
=
pk
->
private_key_mode
?
nvc_lookup
(
pk
,
"Key:"
)
:
NULL
;
if
(
e
==
NULL
)
return
my_error
(
GPG_ERR_MISSING_KEY
);
err
=
assert_value
(
e
);
if
(
err
)
return
err
;
return
gcry_sexp_sscan
(
retsexp
,
NULL
,
e
->
value
,
strlen
(
e
->
value
));
}
/* Set the private key. */
gpg_error_t
nvc_set_private_key
(
nvc_t
pk
,
gcry_sexp_t
sexp
)
{
gpg_error_t
err
;
char
*
raw
,
*
clean
,
*
p
;
size_t
len
,
i
;
if
(
!
pk
->
private_key_mode
)
return
my_error
(
GPG_ERR_MISSING_KEY
);
len
=
gcry_sexp_sprint
(
sexp
,
GCRYSEXP_FMT_ADVANCED
,
NULL
,
0
);
raw
=
xtrymalloc
(
len
);
if
(
raw
==
NULL
)
return
my_error_from_syserror
();
clean
=
xtrymalloc
(
len
);
if
(
clean
==
NULL
)
{
xfree
(
raw
);
return
my_error_from_syserror
();
}
gcry_sexp_sprint
(
sexp
,
GCRYSEXP_FMT_ADVANCED
,
raw
,
len
);
/* Strip any whitespace at the end. */
i
=
strlen
(
raw
)
-
1
;
while
(
i
&&
ascii_isspace
(
raw
[
i
]))
{
raw
[
i
]
=
0
;
i
--
;
}
/* Replace any newlines with spaces, remove superfluous whitespace. */
len
=
strlen
(
raw
);
for
(
p
=
clean
,
i
=
0
;
i
<
len
;
i
++
)
{
char
c
=
raw
[
i
];
/* Collapse contiguous and superfluous spaces. */
if
(
ascii_isspace
(
c
)
&&
i
>
0
&&
(
ascii_isspace
(
raw
[
i
-1
])
||
raw
[
i
-1
]
==
'('
||
raw
[
i
-1
]
==
')'
))
continue
;
if
(
c
==
'\n'
)
c
=
' '
;
*
p
++
=
c
;
}
*
p
=
0
;
err
=
nvc_set
(
pk
,
"Key:"
,
clean
);
xfree
(
raw
);
xfree
(
clean
);
return
err
;
}
/* Parsing and serialization. */
static
gpg_error_t
do_nvc_parse
(
nvc_t
*
result
,
int
*
errlinep
,
estream_t
stream
,
int
for_private_key
)
{
gpg_error_t
err
=
0
;
gpgrt_ssize_t
len
;
char
*
buf
=
NULL
;
size_t
buf_len
=
0
;
char
*
name
=
NULL
;
strlist_t
raw_value
=
NULL
;
*
result
=
for_private_key
?
nvc_new_private_key
()
:
nvc_new
();
if
(
*
result
==
NULL
)
return
my_error_from_syserror
();
if
(
errlinep
)
*
errlinep
=
0
;
while
((
len
=
es_read_line
(
stream
,
&
buf
,
&
buf_len
,
NULL
))
>
0
)
{
char
*
p
;
if
(
errlinep
)
*
errlinep
+=
1
;
/* Skip any whitespace. */
for
(
p
=
buf
;
*
p
&&
ascii_isspace
(
*
p
);
p
++
)
/* Do nothing. */
;
if
(
name
&&
(
spacep
(
buf
)
||
*
p
==
0
))
{
/* A continuation. */
if
(
append_to_strlist_try
(
&
raw_value
,
buf
)
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
continue
;
}
/* No continuation. Add the current entry if any. */
if
(
raw_value
)
{
err
=
_nvc_add
(
*
result
,
name
,
NULL
,
raw_value
,
1
);
if
(
err
)
goto
leave
;
}
/* And prepare for the next one. */
name
=
NULL
;
raw_value
=
NULL
;
if
(
*
p
!=
0
&&
*
p
!=
'#'
)
{
char
*
colon
,
*
value
,
tmp
;
colon
=
strchr
(
buf
,
':'
);
if
(
colon
==
NULL
)
{
err
=
my_error
(
GPG_ERR_INV_VALUE
);
goto
leave
;
}
value
=
colon
+
1
;
tmp
=
*
value
;
*
value
=
0
;
name
=
xtrystrdup
(
p
);
*
value
=
tmp
;
if
(
name
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
if
(
append_to_strlist_try
(
&
raw_value
,
value
)
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
continue
;
}
if
(
append_to_strlist_try
(
&
raw_value
,
buf
)
==
NULL
)
{
err
=
my_error_from_syserror
();
goto
leave
;
}
}
if
(
len
<
0
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
/* Add the final entry. */
if
(
raw_value
)
err
=
_nvc_add
(
*
result
,
name
,
NULL
,
raw_value
,
1
);
leave
:
gpgrt_free
(
buf
);
if
(
err
)
{
nvc_release
(
*
result
);
*
result
=
NULL
;
}
return
err
;
}
/* Parse STREAM and return a newly allocated name value container
structure in RESULT. If ERRLINEP is given, the line number the
parser was last considering is stored there. */
gpg_error_t
nvc_parse
(
nvc_t
*
result
,
int
*
errlinep
,
estream_t
stream
)
{
return
do_nvc_parse
(
result
,
errlinep
,
stream
,
0
);
}
/* Parse STREAM and return a newly allocated name value container
structure in RESULT - assuming the extended private key format. If
ERRLINEP is given, the line number the parser was last considering
is stored there. */
gpg_error_t
nvc_parse_private_key
(
nvc_t
*
result
,
int
*
errlinep
,
estream_t
stream
)
{
return
do_nvc_parse
(
result
,
errlinep
,
stream
,
1
);
}
/* Helper fpr nvc_write. */
static
gpg_error_t
write_one_entry
(
nve_t
entry
,
estream_t
stream
)
{
gpg_error_t
err
;
strlist_t
sl
;
if
(
entry
->
name
)
es_fputs
(
entry
->
name
,
stream
);
err
=
assert_raw_value
(
entry
);
if
(
err
)
return
err
;
for
(
sl
=
entry
->
raw_value
;
sl
;
sl
=
sl
->
next
)
es_fputs
(
sl
->
d
,
stream
);
if
(
es_ferror
(
stream
))
return
my_error_from_syserror
();
return
0
;
}
/* Write a representation of PK to STREAM. */
gpg_error_t
nvc_write
(
nvc_t
pk
,
estream_t
stream
)
{
gpg_error_t
err
=
0
;
nve_t
entry
;
nve_t
keyentry
=
NULL
;
for
(
entry
=
pk
->
first
;
entry
;
entry
=
entry
->
next
)
{
if
(
pk
->
private_key_mode
&&
entry
->
name
&&
!
ascii_strcasecmp
(
entry
->
name
,
"Key:"
))
{
if
(
!
keyentry
)
keyentry
=
entry
;
continue
;
}
err
=
write_one_entry
(
entry
,
stream
);
if
(
err
)
return
err
;
}
/* In private key mode we write the Key always last. */
if
(
keyentry
)
err
=
write_one_entry
(
keyentry
,
stream
);
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, May 10, 8:27 AM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e9/59/e372690696891d4db3eb591d002e
Attached To
rG GnuPG
Event Timeline
Log In to Comment