diff --git a/src/cert-gpgsm.c b/src/cert-gpgsm.c index e8f663b..d3a5728 100644 --- a/src/cert-gpgsm.c +++ b/src/cert-gpgsm.c @@ -1,631 +1,632 @@ /* cert-gpgsm.c - Scute certificate searching. * Copyright (C) 2006, 2007 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "agent.h" #include "cert.h" #include "support.h" #include "debug.h" /* The maximum length of a key listing line. We take the double of * the allowed Assuan line length plus some extra space to avoid a * memmove after a part of a line has been processed. */ #define MAX_LINE_LEN (ASSUAN_LINELENGTH*2 + 200) struct keylist_ctx { /* The pending line in an active key listing. */ char pending[MAX_LINE_LEN + 1]; unsigned int pending_len; /* The current certificate. */ struct cert cert; /* The caller's search callback, invoked for each certificate. */ cert_search_cb_t search_cb; void *search_cb_hook; }; /* Support macros */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) /*** Local prototypes ***/ static gpg_error_t export_cert (const char *fpr, struct cert *cert); /* Release allocated storage for the certificate CERT and reset the certificate. */ static void cert_reset (struct cert *cert) { if (cert->issuer_serial) free (cert->issuer_serial); if (cert->issuer_name) free (cert->issuer_name); if (cert->uid) free (cert->uid); if (cert->cert_der) free (cert->cert_der); memset (cert, '\0', sizeof (struct cert)); } /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since Epoch. Leading spaces are skipped. If ENDP is not NULL, it will point to the next non-parsed character in TIMESTRING. */ static time_t parse_timestamp (const char *timestamp, char **endp) { /* Need to skip leading spaces, because that is what strtoul does but not our ISO 8601 checking code. */ while (*timestamp && *timestamp== ' ') timestamp++; if (!*timestamp) return 0; if (strlen (timestamp) >= 15 && timestamp[8] == 'T') { struct tm buf; int year; year = atoi_4 (timestamp); if (year < 1900) return (time_t)(-1); /* Fixme: We would better use a configure test to see whether mktime can handle dates beyond 2038. */ if (sizeof (time_t) <= 4 && year >= 2038) return (time_t)2145914603; /* 2037-12-31 23:23:23 */ memset (&buf, 0, sizeof buf); buf.tm_year = year - 1900; buf.tm_mon = atoi_2 (timestamp+4) - 1; buf.tm_mday = atoi_2 (timestamp+6); buf.tm_hour = atoi_2 (timestamp+9); buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); if (endp) *endp = (char*)(timestamp + 15); #ifdef HAVE_TIMEGM return timegm (&buf); #else /* FIXME: Need to set TZ to UTC, but that is not thread-safe. */ return mktime (&buf); #endif } else return (time_t)strtoul (timestamp, endp, 10); } /* Decode the C formatted string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. */ static gpg_error_t decode_c_string (const char *src, char **destp, size_t len) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '\\') { *(dest++) = *(src++); continue; } switch (src[1]) { #define DECODE_ONE(match,result) \ case match: \ src += 2; \ *(dest++) = result; \ break; DECODE_ONE ('\'', '\''); DECODE_ONE ('\"', '\"'); DECODE_ONE ('\?', '\?'); DECODE_ONE ('\\', '\\'); DECODE_ONE ('a', '\a'); DECODE_ONE ('b', '\b'); DECODE_ONE ('f', '\f'); DECODE_ONE ('n', '\n'); DECODE_ONE ('r', '\r'); DECODE_ONE ('t', '\t'); DECODE_ONE ('v', '\v'); case 'x': { int val = xtoi_2 (&src[2]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 4; } } break; default: { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); } } } *(dest++) = 0; return 0; } /* Helper for keylist_cb. This fucntion is invoked for each complete * line assembled by keylist_cb. */ static gpg_error_t keylist_cb_line (struct keylist_ctx *ctx) { char *line; enum { RT_NONE, RT_CRT, RT_CRS, RT_FPR, RT_GRP, RT_UID } rectype = RT_NONE; #define NR_FIELDS 16 char *field[NR_FIELDS]; int fields = 0; struct cert *cert; /* Strip a trailing carriage return. */ if (ctx->pending_len > 0 && ctx->pending[ctx->pending_len - 1] == '\r') ctx->pending_len--; ctx->pending[ctx->pending_len] = '\0'; ctx->pending_len = 0; cert = &ctx->cert; line = ctx->pending; while (line && fields < NR_FIELDS) { field[fields++] = line; line = strchr (line, ':'); if (line) *(line++) = '\0'; } if (!strcmp (field[0], "crt")) rectype = RT_CRT; else if (!strcmp (field[0], "crs")) rectype = RT_CRS; else if (!strcmp (field[0], "fpr")) rectype = RT_FPR; else if (!strcmp (field[0], "grp")) rectype = RT_GRP; else if (!strcmp (field[0], "uid")) rectype = RT_UID; else rectype = RT_NONE; switch (rectype) { case RT_CRT: case RT_CRS: /* Reinitialize CERT. */ if (cert->valid) { gpg_error_t err; /* Return the cert. */ err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); if (err) return err; cert_reset (cert); } cert->valid = true; + cert->has_private = (rectype == RT_CRS); #if 0 /* Field 2 has the trust info. */ if (fields >= 2) set_mainkey_trust_info (key, field[1]); #endif /* Field 3 has the key length. */ if (fields >= 3) { int i = atoi (field[2]); /* Ignore invalid values. */ if (i > 1) cert->length = i; } /* Field 4 has the public key algorithm. */ if (fields >= 4) { int i = atoi (field[3]); if (i >= 1 && i < 128) cert->pubkey_algo = i; } /* Field 5 has the long keyid. Allow short key IDs for the output of an external keyserver listing. */ if (fields >= 5 && strlen (field[4]) <= sizeof (cert->keyid) - 1) strcpy (cert->keyid, field[4]); /* Field 6 has the timestamp (seconds). */ if (fields >= 6) cert->timestamp = parse_timestamp (field[5], NULL); /* Field 7 has the expiration time (seconds). */ if (fields >= 7) cert->expires = parse_timestamp (field[6], NULL); /* Field 8 has the X.509 serial number. */ if (fields >= 8) { cert->issuer_serial = strdup (field[7]); if (!cert->issuer_serial) return gpg_error_from_syserror (); } #if 0 /* Field 9 has the ownertrust. */ if (fields >= 9) set_ownertrust (key, field[8]); #endif /* Field 10 is the issuer name. */ if (fields >= 10) if (decode_c_string (field[9], &cert->issuer_name, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ /* Field 11 has the signature class. */ #if 0 /* Field 12 has the capabilities. */ if (fields >= 12) set_mainkey_capability (key, field[11]); #endif break; case RT_UID: if (cert->valid) { /* Field 2 has the trust info, and field 10 has the user ID. Note that more than one UID field can appear. We only remember the last one. It's not used anyway. */ if (fields >= 10 && !cert->uid) { if (decode_c_string (field[9], &cert->uid, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ } } break; case RT_FPR: if (cert->valid) { /* Field 10 has the fingerprint (take only the first one). */ if (fields >= 10 && strlen (field[9]) <= sizeof (cert->fpr) - 1) strcpy (cert->fpr, field[9]); /* Field 13 has the gpgsm chain ID (take only the first one). */ if (fields >= 13 && strlen (field[12]) <= sizeof (cert->chain_id) - 1) strcpy (cert->chain_id, field[12]); } break; case RT_GRP: if (cert->valid) { /* Field 10 has the key grip. */ } break; case RT_NONE: /* Unknown record. */ break; } return 0; } /* This is the data line callback handler provided to assuan_transact * in scute_gpgsm_search_certs_by_{grip,fpr}. It buffers incomplete * lines, and is also used to handle the EOF signal directly outside * of assuan_transact. */ static gpg_error_t keylist_cb (void *hook, const void *line_data, size_t line_len) { struct keylist_ctx *ctx = hook; const char *line = line_data; gpg_error_t err; if (!line) { /* This indicates an EOF. */ /* Check for a pending line, in case GPGSM didn't close with a newline. */ if (ctx->pending_len) { err = keylist_cb_line (ctx); if (err) return err; } /* Check for a pending certificate and return it. */ if (ctx->cert.valid) { err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); } else err = 0; return err; } while (line_len) { if (*line == '\n') { err = keylist_cb_line (ctx); if (err) return err; } else { if (ctx->pending_len >= MAX_LINE_LEN) return gpg_error (GPG_ERR_LINE_TOO_LONG); ctx->pending[ctx->pending_len++] = *line; } line++; line_len--; } return 0; } struct export_hook { /* The exported data. */ char *buffer; /* The length of the exported data buffer. */ unsigned int buffer_len; /* The size of the allocated exported data buffer. */ unsigned int buffer_size; }; #define EXP_DATA_START 4096 static gpg_error_t export_cert_cb (void *hook, const void *line_data, size_t line_len) { struct export_hook *exp = hook; const char *line = line_data; if (exp->buffer_size - exp->buffer_len < line_len) { unsigned int new_buffer_size = exp->buffer_size ? (exp->buffer_size * 2) : EXP_DATA_START; char *new_buffer = realloc (exp->buffer, new_buffer_size); if (!new_buffer) return gpg_error_from_syserror (); exp->buffer = new_buffer; exp->buffer_size = new_buffer_size; } memcpy (exp->buffer + exp->buffer_len, line, line_len); exp->buffer_len += line_len; return 0; } /* Export the certifciate using a second assuan connection. This is * called during the key listing after a "crt" record has been * received. */ static gpg_error_t export_cert (const char *fpr, struct cert *cert) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; #define COMMANDLINELEN 80 char cmd[COMMANDLINELEN]; struct export_hook exp; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "spawning %s\n", get_gpgsm_path ()); return err; } exp.buffer = NULL; exp.buffer_len = 0; exp.buffer_size = 0; snprintf (cmd, sizeof (cmd), "EXPORT --data -- %s", cert->fpr); err = assuan_transact (ctx, cmd, export_cert_cb, &exp, NULL, NULL, NULL, NULL); assuan_release (ctx); if (!err) { cert->cert_der = exp.buffer; cert->cert_der_len = exp.buffer_len; } if (!err) err = scute_agent_is_trusted (fpr, &cert->is_trusted); return err; } /* Search for certificates using a key listing using PATTERN which is * described by MODE. Invoke SEARCH_CB for each certificate found. */ gpg_error_t scute_gpgsm_search_certs (enum keylist_modes mode, const char *pattern, cert_search_cb_t search_cb, void *search_cb_hook) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; char line[ASSUAN_LINELENGTH]; struct keylist_ctx keylist_ctx; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "failed to spawn %s\n", get_gpgsm_path ()); return err; } memset (&keylist_ctx, 0, sizeof keylist_ctx); keylist_ctx.search_cb = search_cb; keylist_ctx.search_cb_hook = search_cb_hook; err = assuan_transact (ctx, "OPTION with-key-data", NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto leave; snprintf (line, sizeof line, "LISTKEYS %s%s", mode == KEYLIST_BY_GRIP? "&":"", pattern); err = assuan_transact (ctx, line, keylist_cb, &keylist_ctx, NULL, NULL, NULL, NULL); if (err) goto leave; /* Signal the EOF. This is not done by Assuan for us. */ err = keylist_cb (&keylist_ctx, NULL, 0); if (err) goto leave; leave: cert_reset (&keylist_ctx.cert); assuan_release (ctx); return err; } diff --git a/src/cert.h b/src/cert.h index f8ebda7..72c7df1 100644 --- a/src/cert.h +++ b/src/cert.h @@ -1,135 +1,138 @@ /* cert.h - Scute certificate management. Copyright (C) 2006, 2007 g10 Code GmbH This file is part of Scute. Scute is free software; you can redistribute it and/or modify it under the terms of 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. Scute 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 Scute; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, g10 Code GmbH gives permission to link this library: with the Mozilla Foundation's code for Mozilla (or with modified versions of it that use the same license as the "Mozilla" code), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "Mozilla". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef CERT_H #define CERT_H 1 #include #include #include #include #include "cryptoki.h" /* A certificate structure holds all information of a certificate during a certificate search. */ struct cert { /* True if we started to fill in a certificate. */ bool valid; #if 1 /* We disable some elements, because they are easy to get from gpgsm but hard to get from the card directly. These fields are only valid when getting the certificate through gpgsm, so don't use them. */ /* The key length. */ int length; /* The public key algorithm. */ int pubkey_algo; /* The key ID. */ unsigned char keyid[17]; /* The X.509 serial number. */ char *issuer_serial; /* The X.509 issuer name. */ char *issuer_name; /* The user ID strings. */ char *uid; /* The timestamp. */ time_t timestamp; /* The expiration time. */ time_t expires; #endif /* The following entries are required to create a PKCS #11 certificate (in cert-object.c). GpgSM delivers them directly, if we get the cert from the card, we need to read them from the cert ourselves. */ /* The fingerprint. */ unsigned char fpr[41]; /* The chain ID as return by a gpgsm key listing. */ unsigned char chain_id[41]; /* The certificate in DER format. This is not entered by the search function, but afterwards by the filter before converting it into a PKCS #11 object. */ unsigned char *cert_der; int cert_der_len; /* If the certificate is trusted or not. For performance reasons, this is not entered by the search function, but afterwards by the filter before converting it into a PKCS #11 object. */ bool is_trusted; + + /* Whether the certificate has a private part. */ + bool has_private; }; /* From cert-gpgsm.c. */ enum keylist_modes { KEYLIST_BY_GRIP, KEYLIST_BY_FPR }; /* The callback type invoked for each certificate found in the search. */ typedef gpg_error_t (*cert_search_cb_t) (void *hook, struct cert *cert); /* Search for certificates using a key listing using PATTERN which is * described by MODE. Invoke SEARCH_CB for each certificate found. */ gpg_error_t scute_gpgsm_search_certs (enum keylist_modes mode, const char *pattern, cert_search_cb_t search_cb, void *search_cb_hook); /* From cert-object.c. */ gpg_error_t scute_attr_cert (struct cert *cert, const char *grip, CK_ATTRIBUTE_PTR *attrp, CK_ULONG *attr_countp); gpg_error_t scute_attr_prv (struct cert *cert, const char *grip, CK_ATTRIBUTE_PTR *attrp, CK_ULONG *attr_countp); void scute_attr_free (CK_ATTRIBUTE_PTR attr, CK_ULONG attr_count); #endif /* !CERT_H */ diff --git a/src/gpgsm.c b/src/gpgsm.c index 026e394..9d6dfd3 100644 --- a/src/gpgsm.c +++ b/src/gpgsm.c @@ -1,141 +1,141 @@ /* gpgsm.c - Talking to gpgsm. * Copyright (C) 2006, 2008 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "cryptoki.h" #include "support.h" #include "cert.h" #include "agent.h" #include "gpgsm.h" #include "debug.h" /* Communication object for search_cb. */ struct search_cb_parm { bool found; /* Set to true if a private key object was found. */ int depth; /* To track the recursion. */ cert_get_cb_t cert_get_cb; void *hook; bool with_chain; const char *grip; }; static gpg_error_t search_cb (void *hook, struct cert *cert) { struct search_cb_parm *ctx = hook; gpg_error_t err = 0; CK_ATTRIBUTE_PTR attrp; CK_ULONG attr_countp; /* Add the private key object only once. */ - if (!ctx->found) + if (!ctx->found && cert->has_private) { err = scute_attr_prv (cert, ctx->grip, &attrp, &attr_countp); if (err) return err; err = (*ctx->cert_get_cb) (ctx->hook, attrp, attr_countp); if (err) { scute_attr_free (attrp, attr_countp); return err; } ctx->found = true; } /* Add the certificate chain recursively before adding the certificate. But ignore errors. If the chain is incomplete, we might still be able to proceed, for example with client authentication. */ if (ctx->with_chain && strcmp (cert->chain_id, cert->fpr)) { ctx->depth++; if (ctx->depth > 7) { DEBUG (DBG_INFO, "search_cb: certificate chain too long\n"); return gpg_error (GPG_ERR_BAD_CERT_CHAIN); } scute_gpgsm_search_certs (KEYLIST_BY_FPR, cert->chain_id, search_cb, ctx); } /* Turn this certificate into a certificate object. */ err = scute_attr_cert (cert, ctx->grip, &attrp, &attr_countp); if (err) return err; err = (*ctx->cert_get_cb) (ctx->hook, attrp, attr_countp); if (err) scute_attr_free (attrp, attr_countp); /* DEBUG (DBG_INFO, "scute_gpgsm_get_cert[%lu]: search_cb result=%d", */ /* (unsigned long)getpid(), err); */ return err; } /* Create the attributes required for a new certificate object. * KINFO->GRIP is used to find the certificate in the local key store * of gpgsm. * * Returns allocated attributes for the certificate object in ATTRP * and ATTR_COUNTP, and for the private key object in PRV_ATTRP and * PRV_ATTR_COUNTP. */ gpg_error_t scute_gpgsm_get_cert (const char *grip, cert_get_cb_t cert_get_cb, void *hook) { gpg_error_t err; struct search_cb_parm search; search.found = false; search.depth = 0; search.cert_get_cb = cert_get_cb; search.hook = hook; search.with_chain = false; search.grip = grip; DEBUG (DBG_INFO, "scute_gpgsm_get_cert: grip='%s'", grip); search.with_chain = true; err = scute_gpgsm_search_certs (KEYLIST_BY_GRIP, grip, search_cb, &search); if (!err) { if (!search.found) err = gpg_error (GPG_ERR_NOT_FOUND); } return err; } diff --git a/src/table.c b/src/table.c index 1101590..96d3d86 100644 --- a/src/table.c +++ b/src/table.c @@ -1,310 +1,310 @@ /* table.c - Indexed table implementation. * Copyright (C) 2006, 2007 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include "table.h" /* Indices are 1 based externally, but 0 based internally. */ #define INDEX_COPY_IN(idx) ((idx) - 1) #define INDEX_COPY_OUT(idx) ((idx) + 1) /* End of table marker. */ #define INDEX_EOT (-1) /* This is an indexed list implementation. It only supports storing and retrieving pointers. One would like to support arbitrary data types inline, but this is not possible in a portable manner, because of aliasing and alignment restrictions. Note that this implementation is only fast if the lists are very short. */ struct scute_table { /* The user data pointers. */ void **data; /* The size of DATA. */ int size; /* The number of used entries in DATA. */ int used; /* The index of the lowest entry that is unused. */ int first_free; /* The index after the highest entry that is used. */ int last_used; /* The allocator and deallocator callback. */ scute_table_alloc_cb_t alloc; scute_table_dealloc_cb_t dealloc; }; /* Some support functions for iteration. */ /* Return the first element in TABLE. */ static int index_first (scute_table_t table) { int index = 0; while (index < table->last_used && table->data[index] == NULL) index++; if (index == table->last_used) return INDEX_EOT; return index; } /* Return the element following INDEX, or the end-of-list marker if INDEX is the last element on the list. */ static int index_next (scute_table_t table, int index) { index++; while (index < table->last_used && table->data[index] == NULL) index++; if (index >= table->last_used) index = INDEX_EOT; return index; } /* TABLE interface implementation. */ /* Create a new table and return it in TABLE_R. */ gpg_error_t scute_table_create (scute_table_t *table_r, scute_table_alloc_cb_t alloc, scute_table_dealloc_cb_t dealloc) { scute_table_t table; table = malloc (sizeof (*table)); if (!table) return gpg_error_from_syserror (); table->data = NULL; table->size = 0; table->used = 0; table->first_free = 0; table->last_used = 0; table->alloc = alloc; table->dealloc = dealloc; *table_r = table; return 0; } /* Destroy the indexed list TABLE. The user has to make sure that the existing entries are not needed anymore before calling this function. */ void scute_table_destroy (scute_table_t table) { int idx = 0; if (table == NULL) return; for (idx = 0; idx < table->last_used; idx++) if (table->data[idx] != NULL) (*table->dealloc) (table->data[idx]); if (table->data) free (table->data); free (table); } /* The initial table size. */ #define TABLE_START_SIZE 4 /* Allocate a new table entry with a free index. Returns the index pointing to the new list entry in INDEX_R. This calls the allocator on the new entry before returning. Also returns the table entry in *DATA_R if this is not NULL. */ gpg_error_t scute_table_alloc (scute_table_t table, int *index_r, void **data_r, void *hook) { gpg_error_t err; int idx; void *data; if (table->used == table->size) { unsigned int size_new = table->size ? 2 * table->size : TABLE_START_SIZE; void *data_new; data_new = realloc (table->data, size_new * sizeof (*(table->data))); if (!data_new) return gpg_error_from_syserror (); table->first_free = table->size; table->data = data_new; table->size = size_new; } /* We may needlessly have increased the table size if this fails, but that is not a problem. */ err = (*table->alloc) (&data, hook); if (err) return err; for (idx = table->first_free; idx < table->last_used; idx++) if (table->data[idx] == NULL) break; /* The following setting for FIRST_FREE is safe, because if this was the last table entry, then the table is full and we will grow the table the next time we are called (if no elements are removed in the meantime. */ table->first_free = idx + 1; if (idx == table->last_used) table->last_used++; table->data[idx] = data; table->used++; *index_r = INDEX_COPY_OUT (idx); if (data_r != NULL) *data_r = data; return 0; } /* Deallocate the list entry index. Afterwards, INDEX points to the following entry. This calls the deallocator on the entry before returning. */ void scute_table_dealloc (scute_table_t table, int *index) { int idx = INDEX_COPY_IN (*index); void *data = NULL; if (idx == INDEX_EOT) return; assert (idx >= 0 && idx < table->last_used); assert (table->data[idx] != NULL); data = table->data[idx]; table->data[idx] = NULL; table->used--; if (idx < table->first_free) table->first_free = idx; /* Update TABLE->last_used if necessary. */ if (idx + 1 == table->last_used) while (table->last_used > 0) { if (table->data[table->last_used - 1] != NULL) break; table->last_used--; } *index = INDEX_COPY_OUT (index_next (table, idx)); (*table->dealloc) (data); } /* Return the iterator for the beginning of the list TABLE. */ int scute_table_first (scute_table_t table) { - if (table->used) + if (table && table->used) { if (table->data[0] != NULL) return INDEX_COPY_OUT (0); else return INDEX_COPY_OUT (index_first (table)); } return 0; } /* Return the index following INDEX. If INDEX is the last element in the list, return 0. */ int scute_table_next (scute_table_t table, int index) { int idx = INDEX_COPY_IN (index); if (idx == INDEX_EOT) return 0; idx = index_next (table, idx); return INDEX_COPY_OUT (idx); } /* Return true iff INDEX is the end-of-list marker. */ bool scute_table_last (scute_table_t table, int index) { (void) table; return INDEX_COPY_IN (index) == INDEX_EOT; } /* Return the user data associated with INDEX. Return NULL if INDEX is not valid. */ void * scute_table_data (scute_table_t table, int index) { int idx = INDEX_COPY_IN (index); if (idx >= 0 && idx < table->last_used) return table->data[idx]; return NULL; } /* Return the number of entries in the table TABLE. */ int scute_table_used (scute_table_t table) { return table->used; }