/* kbnode.c - keyblock node utility functions * Copyright (C) 1998-2012 Free Software Foundation, Inc. * * Author: Timo Schulz * * This file is part of OpenCDK. * * The OpenCDK library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "opencdk.h" #include "main.h" #include "packet.h" /** * cdk_kbnode_new: * @pkt: the packet to add * * Allocates a new key node and adds a packet. **/ cdk_kbnode_t cdk_kbnode_new(cdk_packet_t pkt) { cdk_kbnode_t n; n = cdk_calloc(1, sizeof *n); if (!n) return NULL; n->pkt = pkt; return n; } void _cdk_kbnode_clone(cdk_kbnode_t node) { /* Mark the node as clone which means that the packet will not be freed, just the node itself. */ if (node) node->is_cloned = 1; } /** * cdk_kbnode_release: * @n: the key node * * Releases the memory of the node. **/ void cdk_kbnode_release(cdk_kbnode_t node) { cdk_kbnode_t n2; while (node) { n2 = node->next; if (!node->is_cloned) cdk_pkt_release(node->pkt); cdk_free(node); node = n2; } } /** * cdk_kbnode_delete: * @node: the key node * * Marks the given node as deleted. **/ void cdk_kbnode_delete(cdk_kbnode_t node) { if (node) node->is_deleted = 1; } /* Append NODE to ROOT. ROOT must exist! */ void _cdk_kbnode_add(cdk_kbnode_t root, cdk_kbnode_t node) { cdk_kbnode_t n1; for (n1 = root; n1->next; n1 = n1->next); n1->next = node; } /** * cdk_kbnode_insert: * @root: the root key node * @node: the node to add * @pkttype: packet type * * Inserts @node into the list after @root but before a packet which is not of * type @pkttype (only if @pkttype != 0). **/ void cdk_kbnode_insert(cdk_kbnode_t root, cdk_kbnode_t node, cdk_packet_type_t pkttype) { if (!pkttype) { node->next = root->next; root->next = node; } else { cdk_kbnode_t n1; for (n1 = root; n1->next; n1 = n1->next) if (pkttype != n1->next->pkt->pkttype) { node->next = n1->next; n1->next = node; return; } /* No such packet, append */ node->next = NULL; n1->next = node; } } /** * cdk_kbnode_find_prev: * @root: the root key node * @node: the key node * @pkttype: packet type * * Finds the previous node (if @pkttype = 0) or the previous node * with pkttype @pkttype in the list starting with @root of @node. **/ cdk_kbnode_t cdk_kbnode_find_prev(cdk_kbnode_t root, cdk_kbnode_t node, cdk_packet_type_t pkttype) { cdk_kbnode_t n1; for (n1 = NULL; root && root != node; root = root->next) { if (!pkttype || root->pkt->pkttype == pkttype) n1 = root; } return n1; } /** * cdk_kbnode_find_next: * @node: the key node * @pkttype: packet type * * Ditto, but find the next packet. The behaviour is trivial if * @pkttype is 0 but if it is specified, the next node with a packet * of this type is returned. The function has some knowledge about * the valid ordering of packets: e.g. if the next signature packet * is requested, the function will not return one if it encounters * a user-id. **/ cdk_kbnode_t cdk_kbnode_find_next(cdk_kbnode_t node, cdk_packet_type_t pkttype) { for (node = node->next; node; node = node->next) { if (!pkttype) return node; else if (pkttype == CDK_PKT_USER_ID && (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_SECRET_KEY)) return NULL; else if (pkttype == CDK_PKT_SIGNATURE && (node->pkt->pkttype == CDK_PKT_USER_ID || node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_SECRET_KEY)) return NULL; else if (node->pkt->pkttype == pkttype) return node; } return NULL; } /** * cdk_kbnode_find: * @node: the key node * @pkttype: packet type * * Tries to find the next node with the packettype @pkttype. **/ cdk_kbnode_t cdk_kbnode_find(cdk_kbnode_t node, cdk_packet_type_t pkttype) { for (; node; node = node->next) { if (node->pkt->pkttype == pkttype) return node; } return NULL; } /** * cdk_kbnode_find_packet: * @node: the key node * @pkttype: packet type * * Same as cdk_kbnode_find but it returns the packet instead of the node. **/ cdk_packet_t cdk_kbnode_find_packet(cdk_kbnode_t node, cdk_packet_type_t pkttype) { cdk_kbnode_t res; res = cdk_kbnode_find(node, pkttype); return res ? res->pkt : NULL; } /** * cdk_kbnode_walk: * * Walks through a list of kbnodes. This function returns * the next kbnode for each call; before using the function the first * time, the caller must set CONTEXT to NULL (This has simply the effect * to start with ROOT). */ cdk_kbnode_t cdk_kbnode_walk(cdk_kbnode_t root, cdk_kbnode_t * ctx, int all) { cdk_kbnode_t n; do { if (!*ctx) { *ctx = root; n = root; } else { n = (*ctx)->next; *ctx = n; } } while (!all && n && n->is_deleted); return n; } /** * cdk_kbnode_commit: * @root: the nodes * * Commits changes made to the kblist at ROOT. Note that ROOT my change, * and it is therefore passed by reference. * The function has the effect of removing all nodes marked as deleted. * * Returns: true if any node has been changed */ int cdk_kbnode_commit(cdk_kbnode_t * root) { cdk_kbnode_t n, nl; int changed = 0; for (n = *root, nl = NULL; n; n = nl->next) { if (n->is_deleted) { if (n == *root) *root = nl = n->next; else nl->next = n->next; if (!n->is_cloned) cdk_pkt_release(n->pkt); cdk_free(n); changed = 1; } else nl = n; } return changed; } /** * cdk_kbnode_remove: * @root: the root node * @node: the node to delete * * Removes a node from the root node. */ void cdk_kbnode_remove(cdk_kbnode_t * root, cdk_kbnode_t node) { cdk_kbnode_t n, nl; for (n = *root, nl = NULL; n; n = nl->next) { if (n == node) { if (n == *root) *root = nl = n->next; else nl->next = n->next; if (!n->is_cloned) cdk_pkt_release(n->pkt); cdk_free(n); } else nl = n; } } /** * cdk_cdknode_move: * @root: root node * @node: the node to move * @where: destination place where to move the node. * * Moves NODE behind right after WHERE or to the beginning if WHERE is NULL. */ void cdk_kbnode_move(cdk_kbnode_t * root, cdk_kbnode_t node, cdk_kbnode_t where) { cdk_kbnode_t tmp, prev; if (!root || !*root || !node) return; for (prev = *root; prev && prev->next != node; prev = prev->next); if (!prev) return; /* Node is not in the list */ if (!where) { /* Move node before root */ if (node == *root) return; prev->next = node->next; node->next = *root; *root = node; return; } if (node == where) /* Move it after where. */ return; tmp = node->next; node->next = where->next; where->next = node; prev->next = tmp; } /** * cdk_kbnode_get_packet: * @node: the key node * * Get packet in node. * * Returns: the packet which is stored inside the node in @node. **/ cdk_packet_t cdk_kbnode_get_packet(cdk_kbnode_t node) { if (node) return node->pkt; return NULL; } /** * cdk_kbnode_read_from_mem: * @ret_node: the new key node * @armor: whether base64 or not * @buf: the buffer which stores the key sequence * @buflen: the length of the buffer * * Tries to read a key node from the memory buffer @buf. **/ cdk_error_t cdk_kbnode_read_from_mem(cdk_kbnode_t * ret_node, int armor, const byte * buf, size_t buflen) { cdk_stream_t inp; cdk_error_t rc; if (!ret_node || !buf) return CDK_Inv_Value; *ret_node = NULL; if (!buflen) return gnutls_assert_val(CDK_Too_Short); rc = cdk_stream_tmp_from_mem(buf, buflen, &inp); if (rc) return gnutls_assert_val(rc); if (armor) cdk_stream_set_armor_flag(inp, 0); rc = cdk_keydb_get_keyblock(inp, ret_node); if (rc) gnutls_assert(); cdk_stream_close(inp); return rc; } /** * cdk_kbnode_write_to_mem_alloc: * @node: the key node * @r_buf: buffer to hold the raw data * @r_buflen: buffer length of the allocated raw data. * * The function acts similar to cdk_kbnode_write_to_mem but * it allocates the buffer to avoid the lengthy second run. */ cdk_error_t cdk_kbnode_write_to_mem_alloc(cdk_kbnode_t node, byte ** r_buf, size_t * r_buflen) { cdk_kbnode_t n; cdk_stream_t s; cdk_error_t rc; ssize_t len; if (!node || !r_buf || !r_buflen) { gnutls_assert(); return CDK_Inv_Value; } *r_buf = NULL; *r_buflen = 0; rc = cdk_stream_tmp_new(&s); if (rc) { gnutls_assert(); return rc; } for (n = node; n; n = n->next) { /* Skip all packets which cannot occur in a key composition. */ if (n->pkt->pkttype != CDK_PKT_PUBLIC_KEY && n->pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY && n->pkt->pkttype != CDK_PKT_SECRET_KEY && n->pkt->pkttype != CDK_PKT_SECRET_SUBKEY && n->pkt->pkttype != CDK_PKT_SIGNATURE && n->pkt->pkttype != CDK_PKT_USER_ID && n->pkt->pkttype != CDK_PKT_ATTRIBUTE) continue; rc = cdk_pkt_write(s, n->pkt); if (rc) { cdk_stream_close(s); gnutls_assert(); return rc; } } cdk_stream_seek(s, 0); len = cdk_stream_get_length(s); if (len == 0) return gnutls_assert_val(CDK_General_Error); *r_buf = cdk_calloc(1, len); *r_buflen = cdk_stream_read(s, *r_buf, len); cdk_stream_close(s); return 0; } /** * cdk_kbnode_write_to_mem: * @node: the key node * @buf: the buffer to store the node data * @r_nbytes: the new length of the buffer. * * Tries to write the contents of the key node to the buffer @buf and * return the length of it in @r_nbytes. If buf is (0), only the * length of the node is calculated and returned in @r_nbytes. * Whenever it is possible, the cdk_kbnode_write_to_mem_alloc should be used. **/ cdk_error_t cdk_kbnode_write_to_mem(cdk_kbnode_t node, byte * buf, size_t * r_nbytes) { cdk_kbnode_t n; cdk_stream_t s; cdk_error_t rc; ssize_t len; if (!node || !r_nbytes) { gnutls_assert(); return CDK_Inv_Value; } rc = cdk_stream_tmp_new(&s); if (rc) { gnutls_assert(); return rc; } for (n = node; n; n = n->next) { /* Skip all packets which cannot occur in a key composition. */ if (n->pkt->pkttype != CDK_PKT_PUBLIC_KEY && n->pkt->pkttype != CDK_PKT_PUBLIC_SUBKEY && n->pkt->pkttype != CDK_PKT_SECRET_KEY && n->pkt->pkttype != CDK_PKT_SECRET_SUBKEY && n->pkt->pkttype != CDK_PKT_SIGNATURE && n->pkt->pkttype != CDK_PKT_USER_ID && n->pkt->pkttype != CDK_PKT_ATTRIBUTE) continue; rc = cdk_pkt_write(s, n->pkt); if (rc) { cdk_stream_close(s); gnutls_assert(); return rc; } } cdk_stream_seek(s, 0); len = cdk_stream_get_length(s); if (len == 0) return gnutls_assert_val(CDK_General_Error); if (!buf) { *r_nbytes = len; /* Only return the length of the buffer */ cdk_stream_close(s); return 0; } if (*r_nbytes < (size_t)len) { *r_nbytes = len; rc = CDK_Too_Short; } if (!rc) *r_nbytes = cdk_stream_read(s, buf, len); else gnutls_assert(); cdk_stream_close(s); return rc; } /** * cdk_kbnode_hash: * @node: the key node * @hashctx: uint8_t pointer to the hash context * @is_v4: OpenPGP signature (yes=1, no=0) * @pkttype: packet type to hash (if (0) use the packet type from the node) * @flags: flags which depend on the operation * * Hashes the key node contents. Two modes are supported. If the packet * type is used (!= 0) then the function searches the first node with * this type. Otherwise the node is seen as a single node and the type * is extracted from it. **/ cdk_error_t cdk_kbnode_hash(cdk_kbnode_t node, digest_hd_st * md, int is_v4, cdk_packet_type_t pkttype, int flags) { cdk_packet_t pkt; if (!node || !md) { gnutls_assert(); return CDK_Inv_Value; } if (!pkttype) { pkt = cdk_kbnode_get_packet(node); pkttype = pkt->pkttype; } else { pkt = cdk_kbnode_find_packet(node, pkttype); if (!pkt) { gnutls_assert(); return CDK_Inv_Packet; } } switch (pkttype) { case CDK_PKT_PUBLIC_KEY: case CDK_PKT_PUBLIC_SUBKEY: _cdk_hash_pubkey(pkt->pkt.public_key, md, flags & 1); break; case CDK_PKT_USER_ID: _cdk_hash_userid(pkt->pkt.user_id, is_v4, md); break; case CDK_PKT_SIGNATURE: _cdk_hash_sig_data(pkt->pkt.signature, md); break; default: gnutls_assert(); return CDK_Inv_Mode; } return 0; }