Welcome to mirror list, hosted at ThFree Co, Russian Federation.

misc.h - github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/misc.h
blob: 1b3d324ad76325c4b03f6c1bc6aa67e6f85b8e33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
/*
 * Header for miscellaneous helper functions, mostly defined in the
 * utils subdirectory.
 */

#ifndef PUTTY_MISC_H
#define PUTTY_MISC_H

#include "defs.h"
#include "puttymem.h"
#include "marshal.h"

#include <stdio.h>                     /* for FILE * */
#include <stdarg.h>                    /* for va_list */
#include <stdlib.h>                    /* for abort */
#include <time.h>                      /* for struct tm */
#include <limits.h>                    /* for INT_MAX/MIN */
#include <assert.h>                    /* for assert (obviously) */

unsigned long parse_blocksize(const char *bs);
char ctrlparse(char *s, char **next);

size_t host_strcspn(const char *s, const char *set);
char *host_strchr(const char *s, int c);
char *host_strrchr(const char *s, int c);
char *host_strduptrim(const char *s);

char *dupstr(const char *s);
char *dupcat_fn(const char *s1, ...);
#define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL)
char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2);
char *dupvprintf(const char *fmt, va_list ap);
void burnstr(char *string);

/*
 * The visible part of a strbuf structure. There's a surrounding
 * implementation struct in strbuf.c, which isn't exposed to client
 * code.
 */
struct strbuf {
    char *s;
    unsigned char *u;
    size_t len;
    BinarySink_IMPLEMENTATION;
};

/* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a
 * strbuf constructed using the _nm version will resize itself by
 * alloc/copy/smemclr/free instead of realloc. Use that version for
 * data sensitive enough that it's worth costing performance to
 * avoid copies of it lingering in process memory. */
strbuf *strbuf_new(void);
strbuf *strbuf_new_nm(void);

/* Helpers to allocate a strbuf containing an existing string */
strbuf *strbuf_dup(ptrlen string);
strbuf *strbuf_dup_nm(ptrlen string);

void strbuf_free(strbuf *buf);
void *strbuf_append(strbuf *buf, size_t len);
void strbuf_shrink_to(strbuf *buf, size_t new_len);
void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove);
char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */
static inline void strbuf_clear(strbuf *buf) { strbuf_shrink_to(buf, 0); }
bool strbuf_chomp(strbuf *buf, char char_to_remove);

strbuf *strbuf_new_for_agent_query(void);
void strbuf_finalise_agent_query(strbuf *buf);

/* String-to-Unicode converters that auto-allocate the destination and
 * work around the rather deficient interface of mb_to_wc. */
wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
                     const char *defchr);
char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
                   const char *defchr);

static inline int toint(unsigned u)
{
    /*
     * Convert an unsigned to an int, without running into the
     * undefined behaviour which happens by the strict C standard if
     * the value overflows. You'd hope that sensible compilers would
     * do the sensible thing in response to a cast, but actually I
     * don't trust modern compilers not to do silly things like
     * assuming that _obviously_ you wouldn't have caused an overflow
     * and so they can elide an 'if (i < 0)' test immediately after
     * the cast.
     *
     * Sensible compilers ought of course to optimise this entire
     * function into 'just return the input value', and since it's
     * also declared inline, elide it completely in their output.
     */
    if (u <= (unsigned)INT_MAX)
        return (int)u;
    else if (u >= (unsigned)INT_MIN)   /* wrap in cast _to_ unsigned is OK */
        return INT_MIN + (int)(u - (unsigned)INT_MIN);
    else
        return INT_MIN; /* fallback; should never occur on binary machines */
}

char *fgetline(FILE *fp);
bool read_file_into(BinarySink *bs, FILE *fp);
char *chomp(char *str);
bool strstartswith(const char *s, const char *t);
bool strendswith(const char *s, const char *t);

void base64_encode_atom(const unsigned char *data, int n, char *out);
int base64_decode_atom(const char *atom, unsigned char *out);
void base64_decode_bs(BinarySink *bs, ptrlen data);
void base64_decode_fp(FILE *fp, ptrlen data);
strbuf *base64_decode_sb(ptrlen data);
void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl);
void base64_encode_fp(FILE *fp, ptrlen data, int cpl);
strbuf *base64_encode_sb(ptrlen data, int cpl);
bool base64_valid(ptrlen data);

void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars);
void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars);
strbuf *percent_encode_sb(ptrlen data, const char *badchars);
void percent_decode_bs(BinarySink *bs, ptrlen data);
void percent_decode_fp(FILE *fp, ptrlen data);
strbuf *percent_decode_sb(ptrlen data);

struct bufchain_granule;
struct bufchain_tag {
    struct bufchain_granule *head, *tail;
    size_t buffersize;           /* current amount of buffered data */

    void (*queue_idempotent_callback)(IdempotentCallback *ic);
    IdempotentCallback *ic;
};

void bufchain_init(bufchain *ch);
void bufchain_clear(bufchain *ch);
size_t bufchain_size(bufchain *ch);
void bufchain_add(bufchain *ch, const void *data, size_t len);
ptrlen bufchain_prefix(bufchain *ch);
void bufchain_consume(bufchain *ch, size_t len);
void bufchain_fetch(bufchain *ch, void *data, size_t len);
void bufchain_fetch_consume(bufchain *ch, void *data, size_t len);
bool bufchain_try_consume(bufchain *ch, size_t len);
bool bufchain_try_fetch(bufchain *ch, void *data, size_t len);
bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len);
size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len);
void bufchain_set_callback_inner(
    bufchain *ch, IdempotentCallback *ic,
    void (*queue_idempotent_callback)(IdempotentCallback *ic));
static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic)
{
    extern void queue_idempotent_callback(struct IdempotentCallback *ic);
    /* Wrapper that puts in the standard queue_idempotent_callback
     * function. Lives here rather than in bufchain.c so that
     * standalone programs can use the bufchain facility without this
     * optional callback feature and not need to provide a stub of
     * queue_idempotent_callback. */
    bufchain_set_callback_inner(ch, ic, queue_idempotent_callback);
}

bool validate_manual_hostkey(char *key);

struct tm ltime(void);

/*
 * Special form of strcmp which can cope with NULL inputs. NULL is
 * defined to sort before even the empty string.
 */
int nullstrcmp(const char *a, const char *b);

static inline ptrlen make_ptrlen(const void *ptr, size_t len)
{
    ptrlen pl;
    pl.ptr = ptr;
    pl.len = len;
    return pl;
}

static inline const void *ptrlen_end(ptrlen pl)
{
    return (const char *)pl.ptr + pl.len;
}

static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv)
{
    const char *start = (const char *)startv, *end = (const char *)endv;
    assert(end >= start);
    ptrlen pl;
    pl.ptr = start;
    pl.len = end - start;
    return pl;
}

static inline ptrlen ptrlen_from_asciz(const char *str)
{
    return make_ptrlen(str, strlen(str));
}

static inline ptrlen ptrlen_from_strbuf(strbuf *sb)
{
    return make_ptrlen(sb->u, sb->len);
}

bool ptrlen_eq_string(ptrlen pl, const char *str);
bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2);
int ptrlen_strcmp(ptrlen pl1, ptrlen pl2);
/* ptrlen_startswith and ptrlen_endswith write through their 'tail'
 * argument if and only if it is non-NULL and they return true. Hence
 * you can write ptrlen_startswith(thing, prefix, &thing), writing
 * back to the same ptrlen it read from, to remove a prefix if present
 * and say whether it did so. */
bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
ptrlen ptrlen_get_word(ptrlen *input, const char *separators);
bool ptrlen_contains(ptrlen input, const char *characters);
bool ptrlen_contains_only(ptrlen input, const char *characters);
char *mkstr(ptrlen pl);
int string_length_for_printf(size_t);
/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
#define PTRLEN_PRINTF(pl) \
    string_length_for_printf((pl).len), (const char *)(pl).ptr
/* Make a ptrlen out of a compile-time string literal. We try to
 * enforce that it _is_ a string literal by token-pasting "" on to it,
 * which should provoke a compile error if it's any other kind of
 * string. */
#define PTRLEN_LITERAL(stringlit) \
    TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1))
/* Make a ptrlen out of a compile-time string literal in a way that
 * allows you to declare the ptrlen itself as a compile-time initialiser. */
#define PTRLEN_DECL_LITERAL(stringlit) \
    { TYPECHECK("" stringlit "", stringlit), sizeof(stringlit)-1 }
/* Make a ptrlen out of a constant byte array. */
#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))

void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid);

/* Wipe sensitive data out of memory that's about to be freed. Simpler
 * than memset because we don't need the fill char parameter; also
 * attempts (by fiddly use of volatile) to inhibit the compiler from
 * over-cleverly trying to optimise the memset away because it knows
 * the variable is going out of scope. */
void smemclr(void *b, size_t len);

/* Compare two fixed-length chunks of memory for equality, without
 * data-dependent control flow (so an attacker with a very accurate
 * stopwatch can't try to guess where the first mismatching byte was).
 * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at
 * by the 'eq' in the name. */
unsigned smemeq(const void *av, const void *bv, size_t len);

/* Encode a single UTF-8 character. Assumes that illegal characters
 * (such as things in the surrogate range, or > 0x10FFFF) have already
 * been removed. */
size_t encode_utf8(void *output, unsigned long ch);

/* Encode a wide-character string into UTF-8. Tolerates surrogates if
 * sizeof(wchar_t) == 2, assuming that in that case the wide string is
 * encoded in UTF-16. */
char *encode_wide_string_as_utf8(const wchar_t *wstr);

/* Decode a single UTF-8 character. Returns U+FFFD for any of the
 * illegal cases. */
unsigned long decode_utf8(const char **utf8);

/* Decode a single UTF-8 character to an output buffer of the
 * platform's wchar_t. May write a pair of surrogates if
 * sizeof(wchar_t) == 2, assuming that in that case the wide string is
 * encoded in UTF-16. Otherwise, writes one character. Returns the
 * number written. */
size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out);

/* Write a string out in C string-literal format. */
void write_c_string_literal(FILE *fp, ptrlen str);

char *buildinfo(const char *newline);

/*
 * A function you can put at points in the code where execution should
 * never reach in the first place. Better than assert(false), or even
 * assert(false && "some explanatory message"), because some compilers
 * don't interpret assert(false) as a declaration of unreachability,
 * so they may still warn about pointless things like some variable
 * not being initialised on the unreachable code path.
 *
 * I follow the assertion with a call to abort() just in case someone
 * compiles with -DNDEBUG, and I wrap that abort inside my own
 * function labelled NORETURN just in case some unusual kind of system
 * header wasn't foresighted enough to label abort() itself that way.
 */
static inline NORETURN void unreachable_internal(void) { abort(); }
#define unreachable(msg) (assert(false && msg), unreachable_internal())

/*
 * Debugging functions.
 *
 * Output goes to debug.log
 *
 * debug() is like printf().
 *
 * dmemdump() and dmemdumpl() both do memory dumps.  The difference
 * is that dmemdumpl() is more suited for when the memory address is
 * important (say because you'll be recording pointer values later
 * on).  dmemdump() is more concise.
 */

#ifdef DEBUG
void debug_printf(const char *fmt, ...) PRINTF_LIKE(1, 2);
void debug_memdump(const void *buf, int len, bool L);
#define debug(...) (debug_printf(__VA_ARGS__))
#define dmemdump(buf,len) (debug_memdump(buf, len, false))
#define dmemdumpl(buf,len) (debug_memdump(buf, len, true))
#else
#define debug(...) ((void)0)
#define dmemdump(buf,len) ((void)0)
#define dmemdumpl(buf,len) ((void)0)
#endif

#ifndef lenof
#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
#endif

#ifndef min
#define min(x,y) ( (x) < (y) ? (x) : (y) )
#endif
#ifndef max
#define max(x,y) ( (x) > (y) ? (x) : (y) )
#endif

static inline uint64_t GET_64BIT_LSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint64_t)p[0]      ) | ((uint64_t)p[1] <<  8) |
            ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) |
            ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) |
            ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56));
}

static inline void PUT_64BIT_LSB_FIRST(void *vp, uint64_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[0] = (uint8_t)(value);
    p[1] = (uint8_t)(value >> 8);
    p[2] = (uint8_t)(value >> 16);
    p[3] = (uint8_t)(value >> 24);
    p[4] = (uint8_t)(value >> 32);
    p[5] = (uint8_t)(value >> 40);
    p[6] = (uint8_t)(value >> 48);
    p[7] = (uint8_t)(value >> 56);
}

static inline uint32_t GET_32BIT_LSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint32_t)p[0]      ) | ((uint32_t)p[1] <<  8) |
            ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24));
}

static inline void PUT_32BIT_LSB_FIRST(void *vp, uint32_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[0] = (uint8_t)(value);
    p[1] = (uint8_t)(value >> 8);
    p[2] = (uint8_t)(value >> 16);
    p[3] = (uint8_t)(value >> 24);
}

static inline uint16_t GET_16BIT_LSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint16_t)p[0]      ) | ((uint16_t)p[1] <<  8));
}

static inline void PUT_16BIT_LSB_FIRST(void *vp, uint16_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[0] = (uint8_t)(value);
    p[1] = (uint8_t)(value >> 8);
}

static inline uint64_t GET_64BIT_MSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint64_t)p[7]      ) | ((uint64_t)p[6] <<  8) |
            ((uint64_t)p[5] << 16) | ((uint64_t)p[4] << 24) |
            ((uint64_t)p[3] << 32) | ((uint64_t)p[2] << 40) |
            ((uint64_t)p[1] << 48) | ((uint64_t)p[0] << 56));
}

static inline void PUT_64BIT_MSB_FIRST(void *vp, uint64_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[7] = (uint8_t)(value);
    p[6] = (uint8_t)(value >> 8);
    p[5] = (uint8_t)(value >> 16);
    p[4] = (uint8_t)(value >> 24);
    p[3] = (uint8_t)(value >> 32);
    p[2] = (uint8_t)(value >> 40);
    p[1] = (uint8_t)(value >> 48);
    p[0] = (uint8_t)(value >> 56);
}

static inline uint32_t GET_32BIT_MSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint32_t)p[3]      ) | ((uint32_t)p[2] <<  8) |
            ((uint32_t)p[1] << 16) | ((uint32_t)p[0] << 24));
}

static inline void PUT_32BIT_MSB_FIRST(void *vp, uint32_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[3] = (uint8_t)(value);
    p[2] = (uint8_t)(value >> 8);
    p[1] = (uint8_t)(value >> 16);
    p[0] = (uint8_t)(value >> 24);
}

static inline uint16_t GET_16BIT_MSB_FIRST(const void *vp)
{
    const uint8_t *p = (const uint8_t *)vp;
    return (((uint16_t)p[1]      ) | ((uint16_t)p[0] <<  8));
}

static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value)
{
    uint8_t *p = (uint8_t *)vp;
    p[1] = (uint8_t)(value);
    p[0] = (uint8_t)(value >> 8);
}

/* For use in X11-related applications, an endianness-variable form of
 * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */

static inline uint16_t GET_16BIT_X11(char endian, const void *p)
{
    return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p);
}

static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value)
{
    if (endian == 'B')
        PUT_16BIT_MSB_FIRST(p, value);
    else
        PUT_16BIT_LSB_FIRST(p, value);
}

/* Replace NULL with the empty string, permitting an idiom in which we
 * get a string (pointer,length) pair that might be NULL,0 and can
 * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */
static inline const char *NULLTOEMPTY(const char *s)
{
    return s ? s : "";
}

/* StripCtrlChars, defined in stripctrl.c: an adapter you can put on
 * the front of one BinarySink and which functions as one in turn.
 * Interprets its input as a stream of multibyte characters in the
 * system locale, and removes any that are not either printable
 * characters or newlines. */
struct StripCtrlChars {
    BinarySink_IMPLEMENTATION;
    /* and this is contained in a larger structure */
};
StripCtrlChars *stripctrl_new(
    BinarySink *bs_out, bool permit_cr, wchar_t substitution);
StripCtrlChars *stripctrl_new_term_fn(
    BinarySink *bs_out, bool permit_cr, wchar_t substitution,
    Terminal *term, unsigned long (*translate)(
        Terminal *, term_utf8_decode *, unsigned char));
#define stripctrl_new_term(bs, cr, sub, term) \
    stripctrl_new_term_fn(bs, cr, sub, term, term_translate)
void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out);
void stripctrl_reset(StripCtrlChars *sccpub);
void stripctrl_free(StripCtrlChars *sanpub);
void stripctrl_enable_line_limiting(StripCtrlChars *sccpub);
char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str);
static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str)
{
    return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str));
}

/*
 * A mechanism for loading a file from disk into a memory buffer where
 * it can be picked apart as a BinarySource.
 */
struct LoadedFile {
    char *data;
    size_t len, max_size;
    BinarySource_IMPLEMENTATION;
};
typedef enum {
    LF_OK,      /* file loaded successfully */
    LF_TOO_BIG, /* file didn't fit in buffer */
    LF_ERROR,   /* error from stdio layer */
} LoadFileStatus;
LoadedFile *lf_new(size_t max_size);
void lf_free(LoadedFile *lf);
LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp);
LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename);
static inline ptrlen ptrlen_from_lf(LoadedFile *lf)
{ return make_ptrlen(lf->data, lf->len); }

/* Set the memory block of 'size' bytes at 'out' to the bitwise XOR of
 * the two blocks of the same size at 'in1' and 'in2'.
 *
 * 'out' may point to exactly the same address as one of the inputs,
 * but if the input and output blocks overlap in any other way, the
 * result of this function is not guaranteed. No memmove-style effort
 * is made to handle difficult overlap cases. */
void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);

/* Boolean expressions used in OpenSSH certificate configuration */
bool cert_expr_valid(const char *expression,
                     char **error_msg, ptrlen *error_loc);
bool cert_expr_match_str(const char *expression,
                         const char *hostname, unsigned port);
/* Build a certificate expression out of hostname wildcards. Required
 * to handle legacy configuration from early in development, when
 * multiple wildcards were stored separately in config, implicitly
 * ORed together. */
CertExprBuilder *cert_expr_builder_new(void);
void cert_expr_builder_free(CertExprBuilder *eb);
void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard);
char *cert_expr_expression(CertExprBuilder *eb);

#endif