2033 lines
48 KiB
C
2033 lines
48 KiB
C
/* sexp.c - S-Expression handling
|
||
* Copyright (C) 1999, 2000, 2001, 2002, 2003,
|
||
* 2004, 2006, 2007, 2008, 2011 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of Libgcrypt.
|
||
*
|
||
* Libgcrypt 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.
|
||
*
|
||
* Libgcrypt 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.
|
||
*
|
||
* You should have received a copy of the GNU Lesser 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 <string.h>
|
||
#include <stdarg.h>
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
|
||
#define GCRYPT_NO_MPI_MACROS 1
|
||
#include "g10lib.h"
|
||
|
||
typedef struct gcry_sexp *NODE;
|
||
typedef unsigned short DATALEN;
|
||
|
||
struct gcry_sexp
|
||
{
|
||
byte d[1];
|
||
};
|
||
|
||
#define ST_STOP 0
|
||
#define ST_DATA 1 /* datalen follows */
|
||
#define ST_HINT 2 /* datalen follows */
|
||
#define ST_OPEN 3
|
||
#define ST_CLOSE 4
|
||
|
||
/* the atoi macros assume that the buffer has only valid digits */
|
||
#define atoi_1(p) (*(p) - '0' )
|
||
#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))
|
||
|
||
#define TOKEN_SPECIALS "-./_:*+="
|
||
|
||
static gcry_error_t
|
||
vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *buffer, size_t length, int argflag,
|
||
void **arg_list, va_list arg_ptr);
|
||
|
||
static gcry_error_t
|
||
sexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *buffer, size_t length, int argflag,
|
||
void **arg_list, ...);
|
||
|
||
/* Return true if P points to a byte containing a whitespace according
|
||
to the S-expressions definition. */
|
||
#undef whitespacep
|
||
static GPG_ERR_INLINE int
|
||
whitespacep (const char *p)
|
||
{
|
||
switch (*p)
|
||
{
|
||
case ' ': case '\t': case '\v': case '\f': case '\r': case '\n': return 1;
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
|
||
#if 0
|
||
static void
|
||
dump_mpi( gcry_mpi_t a )
|
||
{
|
||
char buffer[1000];
|
||
size_t n = 1000;
|
||
|
||
if( !a )
|
||
fputs("[no MPI]", stderr );
|
||
else if( gcry_mpi_print( GCRYMPI_FMT_HEX, buffer, &n, a ) )
|
||
fputs("[MPI too large to print]", stderr );
|
||
else
|
||
fputs( buffer, stderr );
|
||
}
|
||
#endif
|
||
|
||
static void
|
||
dump_string (const byte *p, size_t n, int delim )
|
||
{
|
||
for (; n; n--, p++ )
|
||
{
|
||
if ((*p & 0x80) || iscntrl( *p ) || *p == delim )
|
||
{
|
||
if( *p == '\n' )
|
||
log_printf ("\\n");
|
||
else if( *p == '\r' )
|
||
log_printf ("\\r");
|
||
else if( *p == '\f' )
|
||
log_printf ("\\f");
|
||
else if( *p == '\v' )
|
||
log_printf ("\\v");
|
||
else if( *p == '\b' )
|
||
log_printf ("\\b");
|
||
else if( !*p )
|
||
log_printf ("\\0");
|
||
else
|
||
log_printf ("\\x%02x", *p );
|
||
}
|
||
else
|
||
log_printf ("%c", *p);
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
gcry_sexp_dump (const gcry_sexp_t a)
|
||
{
|
||
const byte *p;
|
||
int indent = 0;
|
||
int type;
|
||
|
||
if (!a)
|
||
{
|
||
log_printf ( "[nil]\n");
|
||
return;
|
||
}
|
||
|
||
p = a->d;
|
||
while ( (type = *p) != ST_STOP )
|
||
{
|
||
p++;
|
||
switch ( type )
|
||
{
|
||
case ST_OPEN:
|
||
log_printf ("%*s[open]\n", 2*indent, "");
|
||
indent++;
|
||
break;
|
||
case ST_CLOSE:
|
||
if( indent )
|
||
indent--;
|
||
log_printf ("%*s[close]\n", 2*indent, "");
|
||
break;
|
||
case ST_DATA: {
|
||
DATALEN n;
|
||
memcpy ( &n, p, sizeof n );
|
||
p += sizeof n;
|
||
log_printf ("%*s[data=\"", 2*indent, "" );
|
||
dump_string (p, n, '\"' );
|
||
log_printf ("\"]\n");
|
||
p += n;
|
||
}
|
||
break;
|
||
default:
|
||
log_printf ("%*s[unknown tag %d]\n", 2*indent, "", type);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/****************
|
||
* Pass list through except when it is an empty list - in that case
|
||
* return NULL and release the passed list.
|
||
*/
|
||
static gcry_sexp_t
|
||
normalize ( gcry_sexp_t list )
|
||
{
|
||
unsigned char *p;
|
||
|
||
if ( !list )
|
||
return NULL;
|
||
p = list->d;
|
||
if ( *p == ST_STOP )
|
||
{
|
||
/* this is "" */
|
||
gcry_sexp_release ( list );
|
||
return NULL;
|
||
}
|
||
if ( *p == ST_OPEN && p[1] == ST_CLOSE )
|
||
{
|
||
/* this is "()" */
|
||
gcry_sexp_release ( list );
|
||
return NULL;
|
||
}
|
||
|
||
return list;
|
||
}
|
||
|
||
/* Create a new S-expression object by reading LENGTH bytes from
|
||
BUFFER, assuming it is canonical encoded or autodetected encoding
|
||
when AUTODETECT is set to 1. With FREEFNC not NULL, ownership of
|
||
the buffer is transferred to the newly created object. FREEFNC
|
||
should be the freefnc used to release BUFFER; there is no guarantee
|
||
at which point this function is called; most likey you want to use
|
||
free() or gcry_free().
|
||
|
||
Passing LENGTH and AUTODETECT as 0 is allowed to indicate that
|
||
BUFFER points to a valid canonical encoded S-expression. A LENGTH
|
||
of 0 and AUTODETECT 1 indicates that buffer points to a
|
||
null-terminated string.
|
||
|
||
This function returns 0 and and the pointer to the new object in
|
||
RETSEXP or an error code in which case RETSEXP is set to NULL. */
|
||
gcry_error_t
|
||
gcry_sexp_create (gcry_sexp_t *retsexp, void *buffer, size_t length,
|
||
int autodetect, void (*freefnc)(void*) )
|
||
{
|
||
gcry_error_t errcode;
|
||
gcry_sexp_t se;
|
||
|
||
if (!retsexp)
|
||
return gcry_error (GPG_ERR_INV_ARG);
|
||
*retsexp = NULL;
|
||
if (autodetect < 0 || autodetect > 1 || !buffer)
|
||
return gcry_error (GPG_ERR_INV_ARG);
|
||
|
||
if (!length && !autodetect)
|
||
{ /* What a brave caller to assume that there is really a canonical
|
||
encoded S-expression in buffer */
|
||
length = gcry_sexp_canon_len (buffer, 0, NULL, &errcode);
|
||
if (!length)
|
||
return errcode;
|
||
}
|
||
else if (!length && autodetect)
|
||
{ /* buffer is a string */
|
||
length = strlen ((char *)buffer);
|
||
}
|
||
|
||
errcode = sexp_sscan (&se, NULL, buffer, length, 0, NULL);
|
||
if (errcode)
|
||
return errcode;
|
||
|
||
*retsexp = se;
|
||
if (freefnc)
|
||
{
|
||
/* For now we release the buffer immediately. As soon as we
|
||
have changed the internal represenation of S-expression to
|
||
the canoncial format - which has the advantage of faster
|
||
parsing - we will use this function as a closure in our
|
||
GCRYSEXP object and use the BUFFER directly. */
|
||
freefnc (buffer);
|
||
}
|
||
return gcry_error (GPG_ERR_NO_ERROR);
|
||
}
|
||
|
||
/* Same as gcry_sexp_create but don't transfer ownership */
|
||
gcry_error_t
|
||
gcry_sexp_new (gcry_sexp_t *retsexp, const void *buffer, size_t length,
|
||
int autodetect)
|
||
{
|
||
return gcry_sexp_create (retsexp, (void *)buffer, length, autodetect, NULL);
|
||
}
|
||
|
||
|
||
/****************
|
||
* Release resource of the given SEXP object.
|
||
*/
|
||
void
|
||
gcry_sexp_release( gcry_sexp_t sexp )
|
||
{
|
||
if (sexp)
|
||
{
|
||
if (gcry_is_secure (sexp))
|
||
{
|
||
/* Extra paranoid wiping. */
|
||
const byte *p = sexp->d;
|
||
int type;
|
||
|
||
while ( (type = *p) != ST_STOP )
|
||
{
|
||
p++;
|
||
switch ( type )
|
||
{
|
||
case ST_OPEN:
|
||
break;
|
||
case ST_CLOSE:
|
||
break;
|
||
case ST_DATA:
|
||
{
|
||
DATALEN n;
|
||
memcpy ( &n, p, sizeof n );
|
||
p += sizeof n;
|
||
p += n;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
wipememory (sexp->d, p - sexp->d);
|
||
}
|
||
gcry_free ( sexp );
|
||
}
|
||
}
|
||
|
||
|
||
/****************
|
||
* Make a pair from lists a and b, don't use a or b later on.
|
||
* Special behaviour: If one is a single element list we put the
|
||
* element straight into the new pair.
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_cons( const gcry_sexp_t a, const gcry_sexp_t b )
|
||
{
|
||
(void)a;
|
||
(void)b;
|
||
|
||
/* NYI: Implementation should be quite easy with our new data
|
||
representation */
|
||
BUG ();
|
||
return NULL;
|
||
}
|
||
|
||
|
||
/****************
|
||
* Make a list from all items in the array the end of the array is marked
|
||
* with a NULL.
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_alist( const gcry_sexp_t *array )
|
||
{
|
||
(void)array;
|
||
|
||
/* NYI: Implementation should be quite easy with our new data
|
||
representation. */
|
||
BUG ();
|
||
return NULL;
|
||
}
|
||
|
||
/****************
|
||
* Make a list from all items, the end of list is indicated by a NULL
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_vlist( const gcry_sexp_t a, ... )
|
||
{
|
||
(void)a;
|
||
/* NYI: Implementation should be quite easy with our new data
|
||
representation. */
|
||
BUG ();
|
||
return NULL;
|
||
}
|
||
|
||
|
||
/****************
|
||
* Append n to the list a
|
||
* Returns: a new ist (which maybe a)
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_append( const gcry_sexp_t a, const gcry_sexp_t n )
|
||
{
|
||
(void)a;
|
||
(void)n;
|
||
/* NYI: Implementation should be quite easy with our new data
|
||
representation. */
|
||
BUG ();
|
||
return NULL;
|
||
}
|
||
|
||
gcry_sexp_t
|
||
gcry_sexp_prepend( const gcry_sexp_t a, const gcry_sexp_t n )
|
||
{
|
||
(void)a;
|
||
(void)n;
|
||
/* NYI: Implementation should be quite easy with our new data
|
||
representation. */
|
||
BUG ();
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
/****************
|
||
* Locate token in a list. The token must be the car of a sublist.
|
||
* Returns: A new list with this sublist or NULL if not found.
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_find_token( const gcry_sexp_t list, const char *tok, size_t toklen )
|
||
{
|
||
const byte *p;
|
||
DATALEN n;
|
||
|
||
if ( !list )
|
||
return NULL;
|
||
|
||
if ( !toklen )
|
||
toklen = strlen(tok);
|
||
|
||
p = list->d;
|
||
while ( *p != ST_STOP )
|
||
{
|
||
if ( *p == ST_OPEN && p[1] == ST_DATA )
|
||
{
|
||
const byte *head = p;
|
||
|
||
p += 2;
|
||
memcpy ( &n, p, sizeof n );
|
||
p += sizeof n;
|
||
if ( n == toklen && !memcmp( p, tok, toklen ) )
|
||
{ /* found it */
|
||
gcry_sexp_t newlist;
|
||
byte *d;
|
||
int level = 1;
|
||
|
||
/* Look for the end of the list. */
|
||
for ( p += n; level; p++ )
|
||
{
|
||
if ( *p == ST_DATA )
|
||
{
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--; /* Compensate for later increment. */
|
||
}
|
||
else if ( *p == ST_OPEN )
|
||
{
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE )
|
||
{
|
||
level--;
|
||
}
|
||
else if ( *p == ST_STOP )
|
||
{
|
||
BUG ();
|
||
}
|
||
}
|
||
n = p - head;
|
||
|
||
newlist = gcry_malloc ( sizeof *newlist + n );
|
||
if (!newlist)
|
||
{
|
||
/* No way to return an error code, so we can only
|
||
return Not Found. */
|
||
return NULL;
|
||
}
|
||
d = newlist->d;
|
||
memcpy ( d, head, n ); d += n;
|
||
*d++ = ST_STOP;
|
||
return normalize ( newlist );
|
||
}
|
||
p += n;
|
||
}
|
||
else if ( *p == ST_DATA )
|
||
{
|
||
memcpy ( &n, ++p, sizeof n ); p += sizeof n;
|
||
p += n;
|
||
}
|
||
else
|
||
p++;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/****************
|
||
* Return the length of the given list
|
||
*/
|
||
int
|
||
gcry_sexp_length( const gcry_sexp_t list )
|
||
{
|
||
const byte *p;
|
||
DATALEN n;
|
||
int type;
|
||
int length = 0;
|
||
int level = 0;
|
||
|
||
if ( !list )
|
||
return 0;
|
||
|
||
p = list->d;
|
||
while ( (type=*p) != ST_STOP ) {
|
||
p++;
|
||
if ( type == ST_DATA ) {
|
||
memcpy ( &n, p, sizeof n );
|
||
p += sizeof n + n;
|
||
if ( level == 1 )
|
||
length++;
|
||
}
|
||
else if ( type == ST_OPEN ) {
|
||
if ( level == 1 )
|
||
length++;
|
||
level++;
|
||
}
|
||
else if ( type == ST_CLOSE ) {
|
||
level--;
|
||
}
|
||
}
|
||
return length;
|
||
}
|
||
|
||
|
||
/* Return the internal lengths offset of LIST. That is the size of
|
||
the buffer from the first ST_OPEN, which is retruned at R_OFF, to
|
||
the corresponding ST_CLOSE inclusive. */
|
||
static size_t
|
||
get_internal_buffer (const gcry_sexp_t list, size_t *r_off)
|
||
{
|
||
const unsigned char *p;
|
||
DATALEN n;
|
||
int type;
|
||
int level = 0;
|
||
|
||
*r_off = 0;
|
||
if (list)
|
||
{
|
||
p = list->d;
|
||
while ( (type=*p) != ST_STOP )
|
||
{
|
||
p++;
|
||
if (type == ST_DATA)
|
||
{
|
||
memcpy (&n, p, sizeof n);
|
||
p += sizeof n + n;
|
||
}
|
||
else if (type == ST_OPEN)
|
||
{
|
||
if (!level)
|
||
*r_off = (p-1) - list->d;
|
||
level++;
|
||
}
|
||
else if ( type == ST_CLOSE )
|
||
{
|
||
level--;
|
||
if (!level)
|
||
return p - list->d;
|
||
}
|
||
}
|
||
}
|
||
return 0; /* Not a proper list. */
|
||
}
|
||
|
||
|
||
|
||
/* Extract the CAR of the given list. May return NULL for bad lists
|
||
or memory failure. */
|
||
gcry_sexp_t
|
||
gcry_sexp_nth( const gcry_sexp_t list, int number )
|
||
{
|
||
const byte *p;
|
||
DATALEN n;
|
||
gcry_sexp_t newlist;
|
||
byte *d;
|
||
int level = 0;
|
||
|
||
if ( !list || list->d[0] != ST_OPEN )
|
||
return NULL;
|
||
p = list->d;
|
||
|
||
while ( number > 0 ) {
|
||
p++;
|
||
if ( *p == ST_DATA ) {
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--;
|
||
if ( !level )
|
||
number--;
|
||
}
|
||
else if ( *p == ST_OPEN ) {
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE ) {
|
||
level--;
|
||
if ( !level )
|
||
number--;
|
||
}
|
||
else if ( *p == ST_STOP ) {
|
||
return NULL;
|
||
}
|
||
}
|
||
p++;
|
||
|
||
if ( *p == ST_DATA ) {
|
||
memcpy ( &n, p, sizeof n ); p += sizeof n;
|
||
newlist = gcry_malloc ( sizeof *newlist + n + 1 );
|
||
if (!newlist)
|
||
return NULL;
|
||
d = newlist->d;
|
||
memcpy ( d, p, n ); d += n;
|
||
*d++ = ST_STOP;
|
||
}
|
||
else if ( *p == ST_OPEN ) {
|
||
const byte *head = p;
|
||
|
||
level = 1;
|
||
do {
|
||
p++;
|
||
if ( *p == ST_DATA ) {
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--;
|
||
}
|
||
else if ( *p == ST_OPEN ) {
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE ) {
|
||
level--;
|
||
}
|
||
else if ( *p == ST_STOP ) {
|
||
BUG ();
|
||
}
|
||
} while ( level );
|
||
n = p + 1 - head;
|
||
|
||
newlist = gcry_malloc ( sizeof *newlist + n );
|
||
if (!newlist)
|
||
return NULL;
|
||
d = newlist->d;
|
||
memcpy ( d, head, n ); d += n;
|
||
*d++ = ST_STOP;
|
||
}
|
||
else
|
||
newlist = NULL;
|
||
|
||
return normalize (newlist);
|
||
}
|
||
|
||
gcry_sexp_t
|
||
gcry_sexp_car( const gcry_sexp_t list )
|
||
{
|
||
return gcry_sexp_nth ( list, 0 );
|
||
}
|
||
|
||
|
||
/* Helper to get data from the car. The returned value is valid as
|
||
long as the list is not modified. */
|
||
static const char *
|
||
sexp_nth_data (const gcry_sexp_t list, int number, size_t *datalen)
|
||
{
|
||
const byte *p;
|
||
DATALEN n;
|
||
int level = 0;
|
||
|
||
*datalen = 0;
|
||
if ( !list )
|
||
return NULL;
|
||
|
||
p = list->d;
|
||
if ( *p == ST_OPEN )
|
||
p++; /* Yep, a list. */
|
||
else if (number)
|
||
return NULL; /* Not a list but N > 0 requested. */
|
||
|
||
/* Skip over N elements. */
|
||
while ( number > 0 )
|
||
{
|
||
if ( *p == ST_DATA )
|
||
{
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--;
|
||
if ( !level )
|
||
number--;
|
||
}
|
||
else if ( *p == ST_OPEN )
|
||
{
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE )
|
||
{
|
||
level--;
|
||
if ( !level )
|
||
number--;
|
||
}
|
||
else if ( *p == ST_STOP )
|
||
{
|
||
return NULL;
|
||
}
|
||
p++;
|
||
}
|
||
|
||
/* If this is data, return it. */
|
||
if ( *p == ST_DATA )
|
||
{
|
||
memcpy ( &n, ++p, sizeof n );
|
||
*datalen = n;
|
||
return (const char*)p + sizeof n;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
/* Get data from the car. The returned value is valid as long as the
|
||
list is not modified. */
|
||
const char *
|
||
gcry_sexp_nth_data (const gcry_sexp_t list, int number, size_t *datalen )
|
||
{
|
||
return sexp_nth_data (list, number, datalen);
|
||
}
|
||
|
||
|
||
/* Get a string from the car. The returned value is a malloced string
|
||
and needs to be freed by the caller. */
|
||
char *
|
||
gcry_sexp_nth_string (const gcry_sexp_t list, int number)
|
||
{
|
||
const char *s;
|
||
size_t n;
|
||
char *buf;
|
||
|
||
s = sexp_nth_data (list, number, &n);
|
||
if (!s || n < 1 || (n+1) < 1)
|
||
return NULL;
|
||
buf = gcry_malloc (n+1);
|
||
if (!buf)
|
||
return NULL;
|
||
memcpy (buf, s, n);
|
||
buf[n] = 0;
|
||
return buf;
|
||
}
|
||
|
||
/*
|
||
* Get a MPI from the car
|
||
*/
|
||
gcry_mpi_t
|
||
gcry_sexp_nth_mpi( gcry_sexp_t list, int number, int mpifmt )
|
||
{
|
||
const char *s;
|
||
size_t n;
|
||
gcry_mpi_t a;
|
||
|
||
if ( !mpifmt )
|
||
mpifmt = GCRYMPI_FMT_STD;
|
||
|
||
s = sexp_nth_data (list, number, &n);
|
||
if (!s)
|
||
return NULL;
|
||
|
||
if ( gcry_mpi_scan ( &a, mpifmt, s, n, NULL ) )
|
||
return NULL;
|
||
|
||
return a;
|
||
}
|
||
|
||
|
||
/****************
|
||
* Get the CDR
|
||
*/
|
||
gcry_sexp_t
|
||
gcry_sexp_cdr( const gcry_sexp_t list )
|
||
{
|
||
const byte *p;
|
||
const byte *head;
|
||
DATALEN n;
|
||
gcry_sexp_t newlist;
|
||
byte *d;
|
||
int level = 0;
|
||
int skip = 1;
|
||
|
||
if ( !list || list->d[0] != ST_OPEN )
|
||
return NULL;
|
||
p = list->d;
|
||
|
||
while ( skip > 0 ) {
|
||
p++;
|
||
if ( *p == ST_DATA ) {
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--;
|
||
if ( !level )
|
||
skip--;
|
||
}
|
||
else if ( *p == ST_OPEN ) {
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE ) {
|
||
level--;
|
||
if ( !level )
|
||
skip--;
|
||
}
|
||
else if ( *p == ST_STOP ) {
|
||
return NULL;
|
||
}
|
||
}
|
||
p++;
|
||
|
||
head = p;
|
||
level = 0;
|
||
do {
|
||
if ( *p == ST_DATA ) {
|
||
memcpy ( &n, ++p, sizeof n );
|
||
p += sizeof n + n;
|
||
p--;
|
||
}
|
||
else if ( *p == ST_OPEN ) {
|
||
level++;
|
||
}
|
||
else if ( *p == ST_CLOSE ) {
|
||
level--;
|
||
}
|
||
else if ( *p == ST_STOP ) {
|
||
return NULL;
|
||
}
|
||
p++;
|
||
} while ( level );
|
||
n = p - head;
|
||
|
||
newlist = gcry_malloc ( sizeof *newlist + n + 2 );
|
||
if (!newlist)
|
||
return NULL;
|
||
d = newlist->d;
|
||
*d++ = ST_OPEN;
|
||
memcpy ( d, head, n ); d += n;
|
||
*d++ = ST_CLOSE;
|
||
*d++ = ST_STOP;
|
||
|
||
return normalize (newlist);
|
||
}
|
||
|
||
gcry_sexp_t
|
||
gcry_sexp_cadr ( const gcry_sexp_t list )
|
||
{
|
||
gcry_sexp_t a, b;
|
||
|
||
a = gcry_sexp_cdr ( list );
|
||
b = gcry_sexp_car ( a );
|
||
gcry_sexp_release ( a );
|
||
return b;
|
||
}
|
||
|
||
|
||
|
||
static int
|
||
hextobyte( const byte *s )
|
||
{
|
||
int c=0;
|
||
|
||
if( *s >= '0' && *s <= '9' )
|
||
c = 16 * (*s - '0');
|
||
else if( *s >= 'A' && *s <= 'F' )
|
||
c = 16 * (10 + *s - 'A');
|
||
else if( *s >= 'a' && *s <= 'f' ) {
|
||
c = 16 * (10 + *s - 'a');
|
||
}
|
||
s++;
|
||
if( *s >= '0' && *s <= '9' )
|
||
c += *s - '0';
|
||
else if( *s >= 'A' && *s <= 'F' )
|
||
c += 10 + *s - 'A';
|
||
else if( *s >= 'a' && *s <= 'f' ) {
|
||
c += 10 + *s - 'a';
|
||
}
|
||
return c;
|
||
}
|
||
|
||
struct make_space_ctx {
|
||
gcry_sexp_t sexp;
|
||
size_t allocated;
|
||
byte *pos;
|
||
};
|
||
|
||
static gpg_err_code_t
|
||
make_space ( struct make_space_ctx *c, size_t n )
|
||
{
|
||
size_t used = c->pos - c->sexp->d;
|
||
|
||
if ( used + n + sizeof(DATALEN) + 1 >= c->allocated )
|
||
{
|
||
gcry_sexp_t newsexp;
|
||
byte *newhead;
|
||
size_t newsize;
|
||
|
||
newsize = c->allocated + 2*(n+sizeof(DATALEN)+1);
|
||
if (newsize <= c->allocated)
|
||
return GPG_ERR_TOO_LARGE;
|
||
newsexp = gcry_realloc ( c->sexp, sizeof *newsexp + newsize - 1);
|
||
if (!newsexp)
|
||
return gpg_err_code_from_errno (errno);
|
||
c->allocated = newsize;
|
||
newhead = newsexp->d;
|
||
c->pos = newhead + used;
|
||
c->sexp = newsexp;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Unquote STRING of LENGTH and store it into BUF. The surrounding
|
||
quotes are must already be removed from STRING. We assume that the
|
||
quoted string is syntacillay correct. */
|
||
static size_t
|
||
unquote_string (const char *string, size_t length, unsigned char *buf)
|
||
{
|
||
int esc = 0;
|
||
const unsigned char *s = (const unsigned char*)string;
|
||
unsigned char *d = buf;
|
||
size_t n = length;
|
||
|
||
for (; n; n--, s++)
|
||
{
|
||
if (esc)
|
||
{
|
||
switch (*s)
|
||
{
|
||
case 'b': *d++ = '\b'; break;
|
||
case 't': *d++ = '\t'; break;
|
||
case 'v': *d++ = '\v'; break;
|
||
case 'n': *d++ = '\n'; break;
|
||
case 'f': *d++ = '\f'; break;
|
||
case 'r': *d++ = '\r'; break;
|
||
case '"': *d++ = '\"'; break;
|
||
case '\'': *d++ = '\''; break;
|
||
case '\\': *d++ = '\\'; break;
|
||
|
||
case '\r': /* ignore CR[,LF] */
|
||
if (n>1 && s[1] == '\n')
|
||
{
|
||
s++; n--;
|
||
}
|
||
break;
|
||
|
||
case '\n': /* ignore LF[,CR] */
|
||
if (n>1 && s[1] == '\r')
|
||
{
|
||
s++; n--;
|
||
}
|
||
break;
|
||
|
||
case 'x': /* hex value */
|
||
if (n>2 && hexdigitp (s+1) && hexdigitp (s+2))
|
||
{
|
||
s++; n--;
|
||
*d++ = xtoi_2 (s);
|
||
s++; n--;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
if (n>2 && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
|
||
{
|
||
*d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2);
|
||
s += 2;
|
||
n -= 2;
|
||
}
|
||
break;
|
||
}
|
||
esc = 0;
|
||
}
|
||
else if( *s == '\\' )
|
||
esc = 1;
|
||
else
|
||
*d++ = *s;
|
||
}
|
||
|
||
return d - buf;
|
||
}
|
||
|
||
/****************
|
||
* Scan the provided buffer and return the S expression in our internal
|
||
* format. Returns a newly allocated expression. If erroff is not NULL and
|
||
* a parsing error has occurred, the offset into buffer will be returned.
|
||
* If ARGFLAG is true, the function supports some printf like
|
||
* expressions.
|
||
* These are:
|
||
* %m - MPI
|
||
* %s - string (no autoswitch to secure allocation)
|
||
* %d - integer stored as string (no autoswitch to secure allocation)
|
||
* %b - memory buffer; this takes _two_ arguments: an integer with the
|
||
* length of the buffer and a pointer to the buffer.
|
||
* %S - Copy an gcry_sexp_t here. The S-expression needs to be a
|
||
* regular one, starting with a parenthesis.
|
||
* (no autoswitch to secure allocation)
|
||
* all other format elements are currently not defined and return an error.
|
||
* this includes the "%%" sequence becauce the percent sign is not an
|
||
* allowed character.
|
||
* FIXME: We should find a way to store the secure-MPIs not in the string
|
||
* but as reference to somewhere - this can help us to save huge amounts
|
||
* of secure memory. The problem is, that if only one element is secure, all
|
||
* other elements are automagicaly copied to secure memory too, so the most
|
||
* common operation gcry_sexp_cdr_mpi() will always return a secure MPI
|
||
* regardless whether it is needed or not.
|
||
*/
|
||
static gcry_error_t
|
||
vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *buffer, size_t length, int argflag,
|
||
void **arg_list, va_list arg_ptr)
|
||
{
|
||
gcry_err_code_t err = 0;
|
||
static const char tokenchars[] =
|
||
"abcdefghijklmnopqrstuvwxyz"
|
||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
"0123456789-./_:*+=";
|
||
const char *p;
|
||
size_t n;
|
||
const char *digptr = NULL;
|
||
const char *quoted = NULL;
|
||
const char *tokenp = NULL;
|
||
const char *hexfmt = NULL;
|
||
const char *base64 = NULL;
|
||
const char *disphint = NULL;
|
||
const char *percent = NULL;
|
||
int hexcount = 0;
|
||
int quoted_esc = 0;
|
||
int datalen = 0;
|
||
size_t dummy_erroff;
|
||
struct make_space_ctx c;
|
||
int arg_counter = 0;
|
||
int level = 0;
|
||
|
||
if (!erroff)
|
||
erroff = &dummy_erroff;
|
||
|
||
/* Depending on whether ARG_LIST is non-zero or not, this macro gives
|
||
us the next argument, either from the variable argument list as
|
||
specified by ARG_PTR or from the argument array ARG_LIST. */
|
||
#define ARG_NEXT(storage, type) \
|
||
do \
|
||
{ \
|
||
if (!arg_list) \
|
||
storage = va_arg (arg_ptr, type); \
|
||
else \
|
||
storage = *((type *) (arg_list[arg_counter++])); \
|
||
} \
|
||
while (0)
|
||
|
||
/* The MAKE_SPACE macro is used before each store operation to
|
||
ensure that the buffer is large enough. It requires a global
|
||
context named C and jumps out to the label LEAVE on error! It
|
||
also sets ERROFF using the variables BUFFER and P. */
|
||
#define MAKE_SPACE(n) do { \
|
||
gpg_err_code_t _ms_err = make_space (&c, (n)); \
|
||
if (_ms_err) \
|
||
{ \
|
||
err = _ms_err; \
|
||
*erroff = p - buffer; \
|
||
goto leave; \
|
||
} \
|
||
} while (0)
|
||
|
||
/* The STORE_LEN macro is used to store the length N at buffer P. */
|
||
#define STORE_LEN(p,n) do { \
|
||
DATALEN ashort = (n); \
|
||
memcpy ( (p), &ashort, sizeof(ashort) ); \
|
||
(p) += sizeof (ashort); \
|
||
} while (0)
|
||
|
||
/* We assume that the internal representation takes less memory than
|
||
the provided one. However, we add space for one extra datalen so
|
||
that the code which does the ST_CLOSE can use MAKE_SPACE */
|
||
c.allocated = length + sizeof(DATALEN);
|
||
if (buffer && length && gcry_is_secure (buffer))
|
||
c.sexp = gcry_malloc_secure (sizeof *c.sexp + c.allocated - 1);
|
||
else
|
||
c.sexp = gcry_malloc (sizeof *c.sexp + c.allocated - 1);
|
||
if (!c.sexp)
|
||
{
|
||
err = gpg_err_code_from_errno (errno);
|
||
*erroff = 0;
|
||
goto leave;
|
||
}
|
||
c.pos = c.sexp->d;
|
||
|
||
for (p = buffer, n = length; n; p++, n--)
|
||
{
|
||
if (tokenp && !hexfmt)
|
||
{
|
||
if (strchr (tokenchars, *p))
|
||
continue;
|
||
else
|
||
{
|
||
datalen = p - tokenp;
|
||
MAKE_SPACE (datalen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, datalen);
|
||
memcpy (c.pos, tokenp, datalen);
|
||
c.pos += datalen;
|
||
tokenp = NULL;
|
||
}
|
||
}
|
||
|
||
if (quoted)
|
||
{
|
||
if (quoted_esc)
|
||
{
|
||
switch (*p)
|
||
{
|
||
case 'b': case 't': case 'v': case 'n': case 'f':
|
||
case 'r': case '"': case '\'': case '\\':
|
||
quoted_esc = 0;
|
||
break;
|
||
|
||
case '0': case '1': case '2': case '3': case '4':
|
||
case '5': case '6': case '7':
|
||
if (!((n > 2)
|
||
&& (p[1] >= '0') && (p[1] <= '7')
|
||
&& (p[2] >= '0') && (p[2] <= '7')))
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Invalid octal value. */
|
||
err = GPG_ERR_SEXP_BAD_QUOTATION;
|
||
goto leave;
|
||
}
|
||
p += 2;
|
||
n -= 2;
|
||
quoted_esc = 0;
|
||
break;
|
||
|
||
case 'x':
|
||
if (!((n > 2) && hexdigitp (p+1) && hexdigitp (p+2)))
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Invalid hex value. */
|
||
err = GPG_ERR_SEXP_BAD_QUOTATION;
|
||
goto leave;
|
||
}
|
||
p += 2;
|
||
n -= 2;
|
||
quoted_esc = 0;
|
||
break;
|
||
|
||
case '\r':
|
||
/* ignore CR[,LF] */
|
||
if (n && (p[1] == '\n'))
|
||
{
|
||
p++;
|
||
n--;
|
||
}
|
||
quoted_esc = 0;
|
||
break;
|
||
|
||
case '\n':
|
||
/* ignore LF[,CR] */
|
||
if (n && (p[1] == '\r'))
|
||
{
|
||
p++;
|
||
n--;
|
||
}
|
||
quoted_esc = 0;
|
||
break;
|
||
|
||
default:
|
||
*erroff = p - buffer;
|
||
/* Invalid quoted string escape. */
|
||
err = GPG_ERR_SEXP_BAD_QUOTATION;
|
||
goto leave;
|
||
}
|
||
}
|
||
else if (*p == '\\')
|
||
quoted_esc = 1;
|
||
else if (*p == '\"')
|
||
{
|
||
/* Keep it easy - we know that the unquoted string will
|
||
never be larger. */
|
||
unsigned char *save;
|
||
size_t len;
|
||
|
||
quoted++; /* Skip leading quote. */
|
||
MAKE_SPACE (p - quoted);
|
||
*c.pos++ = ST_DATA;
|
||
save = c.pos;
|
||
STORE_LEN (c.pos, 0); /* Will be fixed up later. */
|
||
len = unquote_string (quoted, p - quoted, c.pos);
|
||
c.pos += len;
|
||
STORE_LEN (save, len);
|
||
quoted = NULL;
|
||
}
|
||
}
|
||
else if (hexfmt)
|
||
{
|
||
if (isxdigit (*p))
|
||
hexcount++;
|
||
else if (*p == '#')
|
||
{
|
||
if ((hexcount & 1))
|
||
{
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_ODD_HEX_NUMBERS;
|
||
goto leave;
|
||
}
|
||
|
||
datalen = hexcount / 2;
|
||
MAKE_SPACE (datalen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, datalen);
|
||
for (hexfmt++; hexfmt < p; hexfmt++)
|
||
{
|
||
if (whitespacep (hexfmt))
|
||
continue;
|
||
*c.pos++ = hextobyte ((const unsigned char*)hexfmt);
|
||
hexfmt++;
|
||
}
|
||
hexfmt = NULL;
|
||
}
|
||
else if (!whitespacep (p))
|
||
{
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_BAD_HEX_CHAR;
|
||
goto leave;
|
||
}
|
||
}
|
||
else if (base64)
|
||
{
|
||
if (*p == '|')
|
||
base64 = NULL;
|
||
}
|
||
else if (digptr)
|
||
{
|
||
if (digitp (p))
|
||
;
|
||
else if (*p == ':')
|
||
{
|
||
datalen = atoi (digptr); /* FIXME: check for overflow. */
|
||
digptr = NULL;
|
||
if (datalen > n - 1)
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Buffer too short. */
|
||
err = GPG_ERR_SEXP_STRING_TOO_LONG;
|
||
goto leave;
|
||
}
|
||
/* Make a new list entry. */
|
||
MAKE_SPACE (datalen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, datalen);
|
||
memcpy (c.pos, p + 1, datalen);
|
||
c.pos += datalen;
|
||
n -= datalen;
|
||
p += datalen;
|
||
}
|
||
else if (*p == '\"')
|
||
{
|
||
digptr = NULL; /* We ignore the optional length. */
|
||
quoted = p;
|
||
quoted_esc = 0;
|
||
}
|
||
else if (*p == '#')
|
||
{
|
||
digptr = NULL; /* We ignore the optional length. */
|
||
hexfmt = p;
|
||
hexcount = 0;
|
||
}
|
||
else if (*p == '|')
|
||
{
|
||
digptr = NULL; /* We ignore the optional length. */
|
||
base64 = p;
|
||
}
|
||
else
|
||
{
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_INV_LEN_SPEC;
|
||
goto leave;
|
||
}
|
||
}
|
||
else if (percent)
|
||
{
|
||
if (*p == 'm' || *p == 'M')
|
||
{
|
||
/* Insert an MPI. */
|
||
gcry_mpi_t m;
|
||
size_t nm = 0;
|
||
int mpifmt = *p == 'm'? GCRYMPI_FMT_STD: GCRYMPI_FMT_USG;
|
||
|
||
ARG_NEXT (m, gcry_mpi_t);
|
||
|
||
if (gcry_mpi_get_flag (m, GCRYMPI_FLAG_OPAQUE))
|
||
{
|
||
void *mp;
|
||
unsigned int nbits;
|
||
|
||
mp = gcry_mpi_get_opaque (m, &nbits);
|
||
nm = (nbits+7)/8;
|
||
if (mp && nm)
|
||
{
|
||
MAKE_SPACE (nm);
|
||
if (!gcry_is_secure (c.sexp->d)
|
||
&& gcry_mpi_get_flag (m, GCRYMPI_FLAG_SECURE))
|
||
{
|
||
/* We have to switch to secure allocation. */
|
||
gcry_sexp_t newsexp;
|
||
byte *newhead;
|
||
|
||
newsexp = gcry_malloc_secure (sizeof *newsexp
|
||
+ c.allocated - 1);
|
||
if (!newsexp)
|
||
{
|
||
err = gpg_err_code_from_errno (errno);
|
||
goto leave;
|
||
}
|
||
newhead = newsexp->d;
|
||
memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
|
||
c.pos = newhead + (c.pos - c.sexp->d);
|
||
gcry_free (c.sexp);
|
||
c.sexp = newsexp;
|
||
}
|
||
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, nm);
|
||
memcpy (c.pos, mp, nm);
|
||
c.pos += nm;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (gcry_mpi_print (mpifmt, NULL, 0, &nm, m))
|
||
BUG ();
|
||
|
||
MAKE_SPACE (nm);
|
||
if (!gcry_is_secure (c.sexp->d)
|
||
&& gcry_mpi_get_flag ( m, GCRYMPI_FLAG_SECURE))
|
||
{
|
||
/* We have to switch to secure allocation. */
|
||
gcry_sexp_t newsexp;
|
||
byte *newhead;
|
||
|
||
newsexp = gcry_malloc_secure (sizeof *newsexp
|
||
+ c.allocated - 1);
|
||
if (!newsexp)
|
||
{
|
||
err = gpg_err_code_from_errno (errno);
|
||
goto leave;
|
||
}
|
||
newhead = newsexp->d;
|
||
memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
|
||
c.pos = newhead + (c.pos - c.sexp->d);
|
||
gcry_free (c.sexp);
|
||
c.sexp = newsexp;
|
||
}
|
||
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, nm);
|
||
if (gcry_mpi_print (mpifmt, c.pos, nm, &nm, m))
|
||
BUG ();
|
||
c.pos += nm;
|
||
}
|
||
}
|
||
else if (*p == 's')
|
||
{
|
||
/* Insert an string. */
|
||
const char *astr;
|
||
size_t alen;
|
||
|
||
ARG_NEXT (astr, const char *);
|
||
alen = strlen (astr);
|
||
|
||
MAKE_SPACE (alen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, alen);
|
||
memcpy (c.pos, astr, alen);
|
||
c.pos += alen;
|
||
}
|
||
else if (*p == 'b')
|
||
{
|
||
/* Insert a memory buffer. */
|
||
const char *astr;
|
||
int alen;
|
||
|
||
ARG_NEXT (alen, int);
|
||
ARG_NEXT (astr, const char *);
|
||
|
||
MAKE_SPACE (alen);
|
||
if (alen
|
||
&& !gcry_is_secure (c.sexp->d)
|
||
&& gcry_is_secure (astr))
|
||
{
|
||
/* We have to switch to secure allocation. */
|
||
gcry_sexp_t newsexp;
|
||
byte *newhead;
|
||
|
||
newsexp = gcry_malloc_secure (sizeof *newsexp
|
||
+ c.allocated - 1);
|
||
if (!newsexp)
|
||
{
|
||
err = gpg_err_code_from_errno (errno);
|
||
goto leave;
|
||
}
|
||
newhead = newsexp->d;
|
||
memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
|
||
c.pos = newhead + (c.pos - c.sexp->d);
|
||
gcry_free (c.sexp);
|
||
c.sexp = newsexp;
|
||
}
|
||
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, alen);
|
||
memcpy (c.pos, astr, alen);
|
||
c.pos += alen;
|
||
}
|
||
else if (*p == 'd')
|
||
{
|
||
/* Insert an integer as string. */
|
||
int aint;
|
||
size_t alen;
|
||
char buf[35];
|
||
|
||
ARG_NEXT (aint, int);
|
||
sprintf (buf, "%d", aint);
|
||
alen = strlen (buf);
|
||
MAKE_SPACE (alen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, alen);
|
||
memcpy (c.pos, buf, alen);
|
||
c.pos += alen;
|
||
}
|
||
else if (*p == 'u')
|
||
{
|
||
/* Insert an unsigned integer as string. */
|
||
unsigned int aint;
|
||
size_t alen;
|
||
char buf[35];
|
||
|
||
ARG_NEXT (aint, unsigned int);
|
||
sprintf (buf, "%u", aint);
|
||
alen = strlen (buf);
|
||
MAKE_SPACE (alen);
|
||
*c.pos++ = ST_DATA;
|
||
STORE_LEN (c.pos, alen);
|
||
memcpy (c.pos, buf, alen);
|
||
c.pos += alen;
|
||
}
|
||
else if (*p == 'S')
|
||
{
|
||
/* Insert a gcry_sexp_t. */
|
||
gcry_sexp_t asexp;
|
||
size_t alen, aoff;
|
||
|
||
ARG_NEXT (asexp, gcry_sexp_t);
|
||
alen = get_internal_buffer (asexp, &aoff);
|
||
if (alen)
|
||
{
|
||
MAKE_SPACE (alen);
|
||
memcpy (c.pos, asexp->d + aoff, alen);
|
||
c.pos += alen;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Invalid format specifier. */
|
||
err = GPG_ERR_SEXP_INV_LEN_SPEC;
|
||
goto leave;
|
||
}
|
||
percent = NULL;
|
||
}
|
||
else if (*p == '(')
|
||
{
|
||
if (disphint)
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Open display hint. */
|
||
err = GPG_ERR_SEXP_UNMATCHED_DH;
|
||
goto leave;
|
||
}
|
||
MAKE_SPACE (0);
|
||
*c.pos++ = ST_OPEN;
|
||
level++;
|
||
}
|
||
else if (*p == ')')
|
||
{
|
||
/* Walk up. */
|
||
if (disphint)
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Open display hint. */
|
||
err = GPG_ERR_SEXP_UNMATCHED_DH;
|
||
goto leave;
|
||
}
|
||
MAKE_SPACE (0);
|
||
*c.pos++ = ST_CLOSE;
|
||
level--;
|
||
}
|
||
else if (*p == '\"')
|
||
{
|
||
quoted = p;
|
||
quoted_esc = 0;
|
||
}
|
||
else if (*p == '#')
|
||
{
|
||
hexfmt = p;
|
||
hexcount = 0;
|
||
}
|
||
else if (*p == '|')
|
||
base64 = p;
|
||
else if (*p == '[')
|
||
{
|
||
if (disphint)
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Open display hint. */
|
||
err = GPG_ERR_SEXP_NESTED_DH;
|
||
goto leave;
|
||
}
|
||
disphint = p;
|
||
}
|
||
else if (*p == ']')
|
||
{
|
||
if (!disphint)
|
||
{
|
||
*erroff = p - buffer;
|
||
/* Open display hint. */
|
||
err = GPG_ERR_SEXP_UNMATCHED_DH;
|
||
goto leave;
|
||
}
|
||
disphint = NULL;
|
||
}
|
||
else if (digitp (p))
|
||
{
|
||
if (*p == '0')
|
||
{
|
||
/* A length may not begin with zero. */
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_ZERO_PREFIX;
|
||
goto leave;
|
||
}
|
||
digptr = p;
|
||
}
|
||
else if (strchr (tokenchars, *p))
|
||
tokenp = p;
|
||
else if (whitespacep (p))
|
||
;
|
||
else if (*p == '{')
|
||
{
|
||
/* fixme: handle rescanning: we can do this by saving our
|
||
current state and start over at p+1 -- Hmmm. At this
|
||
point here we are in a well defined state, so we don't
|
||
need to save it. Great. */
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_UNEXPECTED_PUNC;
|
||
goto leave;
|
||
}
|
||
else if (strchr ("&\\", *p))
|
||
{
|
||
/* Reserved punctuation. */
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_UNEXPECTED_PUNC;
|
||
goto leave;
|
||
}
|
||
else if (argflag && (*p == '%'))
|
||
percent = p;
|
||
else
|
||
{
|
||
/* Bad or unavailable. */
|
||
*erroff = p - buffer;
|
||
err = GPG_ERR_SEXP_BAD_CHARACTER;
|
||
goto leave;
|
||
}
|
||
}
|
||
MAKE_SPACE (0);
|
||
*c.pos++ = ST_STOP;
|
||
|
||
if (level && !err)
|
||
err = GPG_ERR_SEXP_UNMATCHED_PAREN;
|
||
|
||
leave:
|
||
if (err)
|
||
{
|
||
/* Error -> deallocate. */
|
||
if (c.sexp)
|
||
{
|
||
/* Extra paranoid wipe on error. */
|
||
if (gcry_is_secure (c.sexp))
|
||
wipememory (c.sexp, sizeof (struct gcry_sexp) + c.allocated - 1);
|
||
gcry_free (c.sexp);
|
||
}
|
||
/* This might be expected by existing code... */
|
||
*retsexp = NULL;
|
||
}
|
||
else
|
||
*retsexp = normalize (c.sexp);
|
||
|
||
return gcry_error (err);
|
||
#undef MAKE_SPACE
|
||
#undef STORE_LEN
|
||
}
|
||
|
||
|
||
static gcry_error_t
|
||
sexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *buffer, size_t length, int argflag,
|
||
void **arg_list, ...)
|
||
{
|
||
gcry_error_t rc;
|
||
va_list arg_ptr;
|
||
|
||
va_start (arg_ptr, arg_list);
|
||
rc = vsexp_sscan (retsexp, erroff, buffer, length, argflag,
|
||
arg_list, arg_ptr);
|
||
va_end (arg_ptr);
|
||
|
||
return rc;
|
||
}
|
||
|
||
|
||
gcry_error_t
|
||
gcry_sexp_build (gcry_sexp_t *retsexp, size_t *erroff, const char *format, ...)
|
||
{
|
||
gcry_error_t rc;
|
||
va_list arg_ptr;
|
||
|
||
va_start (arg_ptr, format);
|
||
rc = vsexp_sscan (retsexp, erroff, format, strlen(format), 1,
|
||
NULL, arg_ptr);
|
||
va_end (arg_ptr);
|
||
|
||
return rc;
|
||
}
|
||
|
||
|
||
gcry_error_t
|
||
_gcry_sexp_vbuild (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *format, va_list arg_ptr)
|
||
{
|
||
return vsexp_sscan (retsexp, erroff, format, strlen(format), 1,
|
||
NULL, arg_ptr);
|
||
}
|
||
|
||
|
||
/* Like gcry_sexp_build, but uses an array instead of variable
|
||
function arguments. */
|
||
gcry_error_t
|
||
gcry_sexp_build_array (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *format, void **arg_list)
|
||
{
|
||
return sexp_sscan (retsexp, erroff, format, strlen(format), 1, arg_list);
|
||
}
|
||
|
||
|
||
gcry_error_t
|
||
gcry_sexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
|
||
const char *buffer, size_t length)
|
||
{
|
||
return sexp_sscan (retsexp, erroff, buffer, length, 0, NULL);
|
||
}
|
||
|
||
|
||
/* Figure out a suitable encoding for BUFFER of LENGTH.
|
||
Returns: 0 = Binary
|
||
1 = String possible
|
||
2 = Token possible
|
||
*/
|
||
static int
|
||
suitable_encoding (const unsigned char *buffer, size_t length)
|
||
{
|
||
const unsigned char *s;
|
||
int maybe_token = 1;
|
||
|
||
if (!length)
|
||
return 1;
|
||
|
||
for (s=buffer; length; s++, length--)
|
||
{
|
||
if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0))
|
||
&& !strchr ("\b\t\v\n\f\r\"\'\\", *s))
|
||
return 0; /*binary*/
|
||
if ( maybe_token
|
||
&& !alphap (s) && !digitp (s) && !strchr (TOKEN_SPECIALS, *s))
|
||
maybe_token = 0;
|
||
}
|
||
s = buffer;
|
||
if ( maybe_token && !digitp (s) )
|
||
return 2;
|
||
return 1;
|
||
}
|
||
|
||
|
||
static int
|
||
convert_to_hex (const unsigned char *src, size_t len, char *dest)
|
||
{
|
||
int i;
|
||
|
||
if (dest)
|
||
{
|
||
*dest++ = '#';
|
||
for (i=0; i < len; i++, dest += 2 )
|
||
sprintf (dest, "%02X", src[i]);
|
||
*dest++ = '#';
|
||
}
|
||
return len*2+2;
|
||
}
|
||
|
||
static int
|
||
convert_to_string (const unsigned char *s, size_t len, char *dest)
|
||
{
|
||
if (dest)
|
||
{
|
||
char *p = dest;
|
||
*p++ = '\"';
|
||
for (; len; len--, s++ )
|
||
{
|
||
switch (*s)
|
||
{
|
||
case '\b': *p++ = '\\'; *p++ = 'b'; break;
|
||
case '\t': *p++ = '\\'; *p++ = 't'; break;
|
||
case '\v': *p++ = '\\'; *p++ = 'v'; break;
|
||
case '\n': *p++ = '\\'; *p++ = 'n'; break;
|
||
case '\f': *p++ = '\\'; *p++ = 'f'; break;
|
||
case '\r': *p++ = '\\'; *p++ = 'r'; break;
|
||
case '\"': *p++ = '\\'; *p++ = '\"'; break;
|
||
case '\'': *p++ = '\\'; *p++ = '\''; break;
|
||
case '\\': *p++ = '\\'; *p++ = '\\'; break;
|
||
default:
|
||
if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0)))
|
||
{
|
||
sprintf (p, "\\x%02x", *s);
|
||
p += 4;
|
||
}
|
||
else
|
||
*p++ = *s;
|
||
}
|
||
}
|
||
*p++ = '\"';
|
||
return p - dest;
|
||
}
|
||
else
|
||
{
|
||
int count = 2;
|
||
for (; len; len--, s++ )
|
||
{
|
||
switch (*s)
|
||
{
|
||
case '\b':
|
||
case '\t':
|
||
case '\v':
|
||
case '\n':
|
||
case '\f':
|
||
case '\r':
|
||
case '\"':
|
||
case '\'':
|
||
case '\\': count += 2; break;
|
||
default:
|
||
if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0)))
|
||
count += 4;
|
||
else
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
static int
|
||
convert_to_token (const unsigned char *src, size_t len, char *dest)
|
||
{
|
||
if (dest)
|
||
memcpy (dest, src, len);
|
||
return len;
|
||
}
|
||
|
||
|
||
/****************
|
||
* Print SEXP to buffer using the MODE. Returns the length of the
|
||
* SEXP in buffer or 0 if the buffer is too short (We have at least an
|
||
* empty list consisting of 2 bytes). If a buffer of NULL is provided,
|
||
* the required length is returned.
|
||
*/
|
||
size_t
|
||
gcry_sexp_sprint (const gcry_sexp_t list, int mode,
|
||
void *buffer, size_t maxlength )
|
||
{
|
||
static unsigned char empty[3] = { ST_OPEN, ST_CLOSE, ST_STOP };
|
||
const unsigned char *s;
|
||
char *d;
|
||
DATALEN n;
|
||
char numbuf[20];
|
||
size_t len = 0;
|
||
int i, indent = 0;
|
||
|
||
s = list? list->d : empty;
|
||
d = buffer;
|
||
while ( *s != ST_STOP )
|
||
{
|
||
switch ( *s )
|
||
{
|
||
case ST_OPEN:
|
||
s++;
|
||
if ( mode != GCRYSEXP_FMT_CANON )
|
||
{
|
||
if (indent)
|
||
len++;
|
||
len += indent;
|
||
}
|
||
len++;
|
||
if ( buffer )
|
||
{
|
||
if ( len >= maxlength )
|
||
return 0;
|
||
if ( mode != GCRYSEXP_FMT_CANON )
|
||
{
|
||
if (indent)
|
||
*d++ = '\n';
|
||
for (i=0; i < indent; i++)
|
||
*d++ = ' ';
|
||
}
|
||
*d++ = '(';
|
||
}
|
||
indent++;
|
||
break;
|
||
case ST_CLOSE:
|
||
s++;
|
||
len++;
|
||
if ( buffer )
|
||
{
|
||
if ( len >= maxlength )
|
||
return 0;
|
||
*d++ = ')';
|
||
}
|
||
indent--;
|
||
if (*s != ST_OPEN && *s != ST_STOP && mode != GCRYSEXP_FMT_CANON)
|
||
{
|
||
len++;
|
||
len += indent;
|
||
if (buffer)
|
||
{
|
||
if (len >= maxlength)
|
||
return 0;
|
||
*d++ = '\n';
|
||
for (i=0; i < indent; i++)
|
||
*d++ = ' ';
|
||
}
|
||
}
|
||
break;
|
||
case ST_DATA:
|
||
s++;
|
||
memcpy ( &n, s, sizeof n ); s += sizeof n;
|
||
if (mode == GCRYSEXP_FMT_ADVANCED)
|
||
{
|
||
int type;
|
||
size_t nn;
|
||
|
||
switch ( (type=suitable_encoding (s, n)))
|
||
{
|
||
case 1: nn = convert_to_string (s, n, NULL); break;
|
||
case 2: nn = convert_to_token (s, n, NULL); break;
|
||
default: nn = convert_to_hex (s, n, NULL); break;
|
||
}
|
||
len += nn;
|
||
if (buffer)
|
||
{
|
||
if (len >= maxlength)
|
||
return 0;
|
||
switch (type)
|
||
{
|
||
case 1: convert_to_string (s, n, d); break;
|
||
case 2: convert_to_token (s, n, d); break;
|
||
default: convert_to_hex (s, n, d); break;
|
||
}
|
||
d += nn;
|
||
}
|
||
if (s[n] != ST_CLOSE)
|
||
{
|
||
len++;
|
||
if (buffer)
|
||
{
|
||
if (len >= maxlength)
|
||
return 0;
|
||
*d++ = ' ';
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
sprintf (numbuf, "%u:", (unsigned int)n );
|
||
len += strlen (numbuf) + n;
|
||
if ( buffer )
|
||
{
|
||
if ( len >= maxlength )
|
||
return 0;
|
||
d = stpcpy ( d, numbuf );
|
||
memcpy ( d, s, n ); d += n;
|
||
}
|
||
}
|
||
s += n;
|
||
break;
|
||
default:
|
||
BUG ();
|
||
}
|
||
}
|
||
if ( mode != GCRYSEXP_FMT_CANON )
|
||
{
|
||
len++;
|
||
if (buffer)
|
||
{
|
||
if ( len >= maxlength )
|
||
return 0;
|
||
*d++ = '\n';
|
||
}
|
||
}
|
||
if (buffer)
|
||
{
|
||
if ( len >= maxlength )
|
||
return 0;
|
||
*d++ = 0; /* for convenience we make a C string */
|
||
}
|
||
else
|
||
len++; /* we need one byte more for this */
|
||
|
||
return len;
|
||
}
|
||
|
||
|
||
/* Scan a canonical encoded buffer with implicit length values and
|
||
return the actual length this S-expression uses. For a valid S-Exp
|
||
it should never return 0. If LENGTH is not zero, the maximum
|
||
length to scan is given - this can be used for syntax checks of
|
||
data passed from outside. errorcode and erroff may both be passed as
|
||
NULL. */
|
||
size_t
|
||
gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
|
||
size_t *erroff, gcry_error_t *errcode)
|
||
{
|
||
const unsigned char *p;
|
||
const unsigned char *disphint = NULL;
|
||
unsigned int datalen = 0;
|
||
size_t dummy_erroff;
|
||
gcry_error_t dummy_errcode;
|
||
size_t count = 0;
|
||
int level = 0;
|
||
|
||
if (!erroff)
|
||
erroff = &dummy_erroff;
|
||
if (!errcode)
|
||
errcode = &dummy_errcode;
|
||
|
||
*errcode = gcry_error (GPG_ERR_NO_ERROR);
|
||
*erroff = 0;
|
||
if (!buffer)
|
||
return 0;
|
||
if (*buffer != '(')
|
||
{
|
||
*errcode = gcry_error (GPG_ERR_SEXP_NOT_CANONICAL);
|
||
return 0;
|
||
}
|
||
|
||
for (p=buffer; ; p++, count++ )
|
||
{
|
||
if (length && count >= length)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_STRING_TOO_LONG);
|
||
return 0;
|
||
}
|
||
|
||
if (datalen)
|
||
{
|
||
if (*p == ':')
|
||
{
|
||
if (length && (count+datalen) >= length)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_STRING_TOO_LONG);
|
||
return 0;
|
||
}
|
||
count += datalen;
|
||
p += datalen;
|
||
datalen = 0;
|
||
}
|
||
else if (digitp(p))
|
||
datalen = datalen*10 + atoi_1(p);
|
||
else
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_INV_LEN_SPEC);
|
||
return 0;
|
||
}
|
||
}
|
||
else if (*p == '(')
|
||
{
|
||
if (disphint)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_UNMATCHED_DH);
|
||
return 0;
|
||
}
|
||
level++;
|
||
}
|
||
else if (*p == ')')
|
||
{ /* walk up */
|
||
if (!level)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_UNMATCHED_PAREN);
|
||
return 0;
|
||
}
|
||
if (disphint)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_UNMATCHED_DH);
|
||
return 0;
|
||
}
|
||
if (!--level)
|
||
return ++count; /* ready */
|
||
}
|
||
else if (*p == '[')
|
||
{
|
||
if (disphint)
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_NESTED_DH);
|
||
return 0;
|
||
}
|
||
disphint = p;
|
||
}
|
||
else if (*p == ']')
|
||
{
|
||
if ( !disphint )
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_UNMATCHED_DH);
|
||
return 0;
|
||
}
|
||
disphint = NULL;
|
||
}
|
||
else if (digitp (p) )
|
||
{
|
||
if (*p == '0')
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_ZERO_PREFIX);
|
||
return 0;
|
||
}
|
||
datalen = atoi_1 (p);
|
||
}
|
||
else if (*p == '&' || *p == '\\')
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_UNEXPECTED_PUNC);
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
*erroff = count;
|
||
*errcode = gcry_error (GPG_ERR_SEXP_BAD_CHARACTER);
|
||
return 0;
|
||
}
|
||
}
|
||
}
|