static void sungem_tx_kick(SunGEMState *s)
{
    PCIDevice *d = PCI_DEVICE(s);
    uint32_t comp, kick;
    uint32_t txdma_cfg, txmac_cfg, ints;
    uint64_t dbase;

    trace_sungem_tx_kick();

    /* Check that both TX MAC and TX DMA are enabled. We don't
     * handle DMA-less direct FIFO operations (we don't emulate
     * the FIFO at all).
     *
     * A write to TXDMA_KICK while DMA isn't enabled can happen
     * when the driver is resetting the pointer.
     */
    txdma_cfg = s->txdmaregs[TXDMA_CFG >> 2];
    txmac_cfg = s->macregs[MAC_TXCFG >> 2];
    if (!(txdma_cfg & TXDMA_CFG_ENABLE) ||
        !(txmac_cfg & MAC_TXCFG_ENAB)) {
        trace_sungem_tx_disabled();
        return;
    }

    /* XXX Test min frame size register ? */
    /* XXX Test max frame size register ? */

    dbase = s->txdmaregs[TXDMA_DBHI >> 2];
    dbase = (dbase << 32) | s->txdmaregs[TXDMA_DBLOW >> 2];

    comp = s->txdmaregs[TXDMA_TXDONE >> 2] & s->tx_mask;
    kick = s->txdmaregs[TXDMA_KICK >> 2] & s->tx_mask;

    trace_sungem_tx_process(comp, kick, s->tx_mask + 1);

    /* This is rather primitive for now, we just send everything we
     * can in one go, like e1000. Ideally we should do the sending
     * from some kind of background task
     */
    while (comp != kick) {
        struct gem_txd desc;

        /* Read the next descriptor */
        pci_dma_read(d, dbase + comp * sizeof(desc), &desc, sizeof(desc));

        /* Byteswap descriptor */
        desc.control_word = le64_to_cpu(desc.control_word);
        desc.buffer = le64_to_cpu(desc.buffer);
        trace_sungem_tx_desc(comp, desc.control_word, desc.buffer);

        /* Send it for processing */
        sungem_process_tx_desc(s, &desc);

        /* Interrupt */
        ints = GREG_STAT_TXDONE;
        if (desc.control_word & TXDCTRL_INTME) {
            ints |= GREG_STAT_TXINTME;
        }
        sungem_update_status(s, ints, true);

        /* Next ! */
        comp = (comp + 1) & s->tx_mask;
        s->txdmaregs[TXDMA_TXDONE >> 2] = comp;
    }

    /* We sent everything, set status/irq bit */
    sungem_update_status(s, GREG_STAT_TXALL, true);
}


static bool sungem_can_receive(NetClientState *nc)
{
    SunGEMState *s = qemu_get_nic_opaque(nc);
    uint32_t kick, done, rxdma_cfg, rxmac_cfg;
    bool full;

    rxmac_cfg = s->macregs[MAC_RXCFG >> 2];
    rxdma_cfg = s->rxdmaregs[RXDMA_CFG >> 2];

    /* If MAC disabled, can't receive */
    if ((rxmac_cfg & MAC_RXCFG_ENAB) == 0) {
        trace_sungem_rx_mac_disabled();
        return false;
    }
    if ((rxdma_cfg & RXDMA_CFG_ENABLE) == 0) {
        trace_sungem_rx_txdma_disabled();
        return false;
    }

    /* Check RX availability */
    kick = s->rxdmaregs[RXDMA_KICK >> 2];
    done = s->rxdmaregs[RXDMA_DONE >> 2];
    full = sungem_rx_full(s, kick, done);

    trace_sungem_rx_check(!full, kick, done);

    return !full;
}

sungem: switch to use qemu_receive_packet() for loopback

This patch switches to use qemu_receive_packet() which can detect
reentrancy and return early.

This is intended to address CVE-2021-3416.

Cc: Prasad J Pandit <ppandit@redhat.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>


static uint64_t sungem_mmio_mif_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr <= 0x1c)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown MIF register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->mifregs[addr >> 2];

    trace_sungem_mmio_mif_read(addr, val);

    return val;
}


static bool sungem_rx_full(SunGEMState *s, uint32_t kick, uint32_t done)
{
    return kick == ((done + 1) & s->rx_mask);
}


static void sungem_update_status(SunGEMState *s, uint32_t bits, bool val)
{
    uint32_t stat;

    stat = s->gregs[GREG_STAT >> 2];
    if (val) {
        stat |= bits;
    } else {
        stat &= ~bits;
    }
    s->gregs[GREG_STAT >> 2] = stat;
    sungem_eval_irq(s);
}


static void sungem_eval_irq(SunGEMState *s)
{
    uint32_t stat, mask;

    mask = s->gregs[GREG_IMASK >> 2];
    stat = s->gregs[GREG_STAT >> 2] & ~GREG_STAT_TXNR;
    if (stat & ~mask) {
        pci_set_irq(PCI_DEVICE(s), 1);
    } else {
        pci_set_irq(PCI_DEVICE(s), 0);
    }
}


static void sungem_reset_rx(SunGEMState *s)
{
    trace_sungem_rx_reset();

    /* XXX Do RXCFG */
    /* XXX Check value */
    s->rxdmaregs[RXDMA_FSZ >> 2] = 0x140;
    s->rxdmaregs[RXDMA_DONE >> 2] = 0;
    s->rxdmaregs[RXDMA_KICK >> 2] = 0;
    s->rxdmaregs[RXDMA_CFG >> 2] = 0x1000010;
    s->rxdmaregs[RXDMA_PTHRESH >> 2] = 0xf8;
    s->rxdmaregs[RXDMA_BLANK >> 2] = 0;

    sungem_update_masks(s);
}


static int sungem_check_rx_mac(SunGEMState *s, const uint8_t *mac, uint32_t crc)
{
    uint32_t rxcfg = s->macregs[MAC_RXCFG >> 2];
    uint32_t mac0, mac1, mac2;

    /* Promisc enabled ? */
    if (rxcfg & MAC_RXCFG_PROM) {
        return rx_match_promisc;
    }

    /* Format MAC address into dwords */
    mac0 = (mac[4] << 8) | mac[5];
    mac1 = (mac[2] << 8) | mac[3];
    mac2 = (mac[0] << 8) | mac[1];

    trace_sungem_rx_mac_check(mac0, mac1, mac2);

    /* Is this a broadcast frame ? */
    if (mac0 == 0xffff && mac1 == 0xffff && mac2 == 0xffff) {
        return rx_match_bcast;
    }

    /* TODO: Implement address filter registers (or we don't care ?) */

    /* Is this a multicast frame ? */
    if (mac[0] & 1) {
        trace_sungem_rx_mac_multicast();

        /* Promisc group enabled ? */
        if (rxcfg & MAC_RXCFG_PGRP) {
            return rx_match_allmcast;
        }

        /* TODO: Check MAC control frames (or we don't care) ? */

        /* Check hash filter (somebody check that's correct ?) */
        if (rxcfg & MAC_RXCFG_HFE) {
            uint32_t hash, idx;

            crc >>= 24;
            idx = (crc >> 2) & 0x3c;
            hash = s->macregs[(MAC_HASH0 + idx) >> 2];
            if (hash & (1 << (15 - (crc & 0xf)))) {
                return rx_match_mcast;
            }
        }
        return rx_no_match;
    }

    /* Main MAC check */
    trace_sungem_rx_mac_compare(s->macregs[MAC_ADDR0 >> 2],
                                s->macregs[MAC_ADDR1 >> 2],
                                s->macregs[MAC_ADDR2 >> 2]);

    if (mac0 == s->macregs[MAC_ADDR0 >> 2] &&
        mac1 == s->macregs[MAC_ADDR1 >> 2] &&
        mac2 == s->macregs[MAC_ADDR2 >> 2]) {
        return rx_match_mac;
    }

    /* Alt MAC check */
    if (mac0 == s->macregs[MAC_ADDR3 >> 2] &&
        mac1 == s->macregs[MAC_ADDR4 >> 2] &&
        mac2 == s->macregs[MAC_ADDR5 >> 2]) {
        return rx_match_altmac;
    }

    return rx_no_match;
}


static ssize_t sungem_receive(NetClientState *nc, const uint8_t *buf,
                              size_t size)
{
    SunGEMState *s = qemu_get_nic_opaque(nc);
    PCIDevice *d = PCI_DEVICE(s);
    uint32_t mac_crc, done, kick, max_fsize;
    uint32_t fcs_size, ints, rxdma_cfg, rxmac_cfg, csum, coff;
    uint8_t smallbuf[60];
    struct gem_rxd desc;
    uint64_t dbase, baddr;
    unsigned int rx_cond;

    trace_sungem_rx_packet(size);

    rxmac_cfg = s->macregs[MAC_RXCFG >> 2];
    rxdma_cfg = s->rxdmaregs[RXDMA_CFG >> 2];
    max_fsize = s->macregs[MAC_MAXFSZ >> 2] & 0x7fff;

    /* If MAC or DMA disabled, can't receive */
    if (!(rxdma_cfg & RXDMA_CFG_ENABLE) ||
        !(rxmac_cfg & MAC_RXCFG_ENAB)) {
        trace_sungem_rx_disabled();
        return 0;
    }

    /* Size adjustment for FCS */
    if (rxmac_cfg & MAC_RXCFG_SFCS) {
        fcs_size = 0;
    } else {
        fcs_size = 4;
    }

    /* Discard frame smaller than a MAC or larger than max frame size
     * (when accounting for FCS)
     */
    if (size < 6 || (size + 4) > max_fsize) {
        trace_sungem_rx_bad_frame_size(size);
        /* XXX Increment error statistics ? */
        return size;
    }

    /* We don't drop too small frames since we get them in qemu, we pad
     * them instead. We should probably use the min frame size register
     * but I don't want to use a variable size staging buffer and I
     * know both MacOS and Linux use the default 64 anyway. We use 60
     * here to account for the non-existent FCS.
     */
    if (size < 60) {
        memcpy(smallbuf, buf, size);
        memset(&smallbuf[size], 0, 60 - size);
        buf = smallbuf;
        size = 60;
    }

    /* Get MAC crc */
    mac_crc = net_crc32_le(buf, ETH_ALEN);

    /* Packet isn't for me ? */
    rx_cond = sungem_check_rx_mac(s, buf, mac_crc);
    if (rx_cond == rx_no_match) {
        /* Just drop it */
        trace_sungem_rx_unmatched();
        return size;
    }

    /* Get ring pointers */
    kick = s->rxdmaregs[RXDMA_KICK >> 2] & s->rx_mask;
    done = s->rxdmaregs[RXDMA_DONE >> 2] & s->rx_mask;

    trace_sungem_rx_process(done, kick, s->rx_mask + 1);

    /* Ring full ? Can't receive */
    if (sungem_rx_full(s, kick, done)) {
        trace_sungem_rx_ringfull();
        return 0;
    }

    /* Note: The real GEM will fetch descriptors in blocks of 4,
     * for now we handle them one at a time, I think the driver will
     * cope
     */

    dbase = s->rxdmaregs[RXDMA_DBHI >> 2];
    dbase = (dbase << 32) | s->rxdmaregs[RXDMA_DBLOW >> 2];

    /* Read the next descriptor */
    pci_dma_read(d, dbase + done * sizeof(desc), &desc, sizeof(desc));

    trace_sungem_rx_desc(le64_to_cpu(desc.status_word),
                         le64_to_cpu(desc.buffer));

    /* Effective buffer address */
    baddr = le64_to_cpu(desc.buffer) & ~7ull;
    baddr |= (rxdma_cfg & RXDMA_CFG_FBOFF) >> 10;

    /* Write buffer out */
    pci_dma_write(d, baddr, buf, size);

    if (fcs_size) {
        /* Should we add an FCS ? Linux doesn't ask us to strip it,
         * however I believe nothing checks it... For now we just
         * do nothing. It's faster this way.
         */
    }

    /* Calculate the checksum */
    coff = (rxdma_cfg & RXDMA_CFG_CSUMOFF) >> 13;
    csum = net_raw_checksum((uint8_t *)buf + coff, size - coff);

    /* Build the updated descriptor */
    desc.status_word = (size + fcs_size) << 16;
    desc.status_word |= ((uint64_t)(mac_crc >> 16)) << 44;
    desc.status_word |= csum;
    if (rx_cond == rx_match_mcast) {
        desc.status_word |= RXDCTRL_HPASS;
    }
    if (rx_cond == rx_match_altmac) {
        desc.status_word |= RXDCTRL_ALTMAC;
    }
    desc.status_word = cpu_to_le64(desc.status_word);

    pci_dma_write(d, dbase + done * sizeof(desc), &desc, sizeof(desc));

    done = (done + 1) & s->rx_mask;
    s->rxdmaregs[RXDMA_DONE >> 2] = done;

    /* XXX Unconditionally set RX interrupt for now. The interrupt
     * mitigation timer might well end up adding more overhead than
     * helping here...
     */
    ints = GREG_STAT_RXDONE;
    if (sungem_rx_full(s, kick, done)) {
        ints |= GREG_STAT_RXNOBUF;
    }
    sungem_update_status(s, ints, true);

    return size;
}


static uint64_t sungem_mmio_pcs_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr <= 0x18) && !(addr >= 0x50 && addr <= 0x5c)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown PCS register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->pcsregs[addr >> 2];

    trace_sungem_mmio_pcs_read(addr, val);

    return val;
}


static uint64_t sungem_mmio_mac_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr <= 0x134)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown MAC register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->macregs[addr >> 2];

    trace_sungem_mmio_mac_read(addr, val);

    switch (addr) {
    case MAC_TXSTAT:
        /* Side effect, clear all */
        s->macregs[addr >> 2] = 0;
        sungem_update_status(s, GREG_STAT_TXMAC, false);
        break;
    case MAC_RXSTAT:
        /* Side effect, clear all */
        s->macregs[addr >> 2] = 0;
        sungem_update_status(s, GREG_STAT_RXMAC, false);
        break;
    case MAC_CSTAT:
        /* Side effect, interrupt bits */
        s->macregs[addr >> 2] &= MAC_CSTAT_PTR;
        sungem_update_status(s, GREG_STAT_MAC, false);
        break;
    }

    return val;
}


static void sungem_mii_write(SunGEMState *s, uint8_t phy_addr,
                             uint8_t reg_addr, uint16_t val)
{
    trace_sungem_mii_write(phy_addr, reg_addr, val);

    /* XXX TODO */
}


static void sungem_reset_tx(SunGEMState *s)
{
    trace_sungem_tx_reset();

    /* XXX Do TXCFG */
    /* XXX Check value */
    s->txdmaregs[TXDMA_FSZ >> 2] = 0x90;
    s->txdmaregs[TXDMA_TXDONE >> 2] = 0;
    s->txdmaregs[TXDMA_KICK >> 2] = 0;
    s->txdmaregs[TXDMA_CFG >> 2] = 0x118010;

    sungem_update_masks(s);

    s->tx_size = 0;
    s->tx_first_ctl = 0;
}


static void sungem_mmio_rxdma_write(void *opaque, hwaddr addr, uint64_t val,
                                    unsigned size)
{
    SunGEMState *s = opaque;

    if (!(addr <= 0x28) && !(addr >= 0x100 && addr <= 0x120)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Write to unknown RXDMA register 0x%"HWADDR_PRIx"\n",
                      addr);
        return;
    }

    trace_sungem_mmio_rxdma_write(addr, val);

    /* Pre-write filter */
    switch (addr) {
    /* Read only registers */
    case RXDMA_DONE:
    case RXDMA_PCNT:
    case RXDMA_SMACHINE:
    case RXDMA_DPLOW:
    case RXDMA_DPHI:
    case RXDMA_FSZ:
    case RXDMA_FTAG:
        return; /* No actual write */
    }

    s->rxdmaregs[addr >> 2] = val;

    /* Post write action */
    switch (addr) {
    case RXDMA_KICK:
        trace_sungem_rx_kick(val);
        break;
    case RXDMA_CFG:
        sungem_update_masks(s);
        if ((s->macregs[MAC_RXCFG >> 2] & MAC_RXCFG_ENAB) != 0 &&
            (s->rxdmaregs[RXDMA_CFG >> 2] & RXDMA_CFG_ENABLE) != 0) {
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;
    }
}


static uint64_t sungem_mmio_rxdma_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr <= 0x28) && !(addr >= 0x100 && addr <= 0x120)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown RXDMA register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->rxdmaregs[addr >> 2];

    trace_sungem_mmio_rxdma_read(addr, val);

    return val;
}


static void sungem_mmio_mac_write(void *opaque, hwaddr addr, uint64_t val,
                                  unsigned size)
{
    SunGEMState *s = opaque;

    if (!(addr <= 0x134)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Write to unknown MAC register 0x%"HWADDR_PRIx"\n",
                      addr);
        return;
    }

    trace_sungem_mmio_mac_write(addr, val);

    /* Pre-write filter */
    switch (addr) {
    /* Read only registers */
    case MAC_TXRST: /* Not technically read-only but will do for now */
    case MAC_RXRST: /* Not technically read-only but will do for now */
    case MAC_TXSTAT:
    case MAC_RXSTAT:
    case MAC_CSTAT:
    case MAC_PATMPS:
    case MAC_SMACHINE:
        return; /* No actual write */
    case MAC_MINFSZ:
        /* 10-bits implemented */
        val &= 0x3ff;
        break;
    }

    s->macregs[addr >> 2] = val;

    /* Post write action */
    switch (addr) {
    case MAC_TXMASK:
    case MAC_RXMASK:
    case MAC_MCMASK:
        sungem_eval_cascade_irq(s);
        break;
    case MAC_RXCFG:
        sungem_update_masks(s);
        if ((s->macregs[MAC_RXCFG >> 2] & MAC_RXCFG_ENAB) != 0 &&
            (s->rxdmaregs[RXDMA_CFG >> 2] & RXDMA_CFG_ENABLE) != 0) {
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;
    }
}


static uint64_t sungem_mmio_txdma_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr < 0x38) && !(addr >= 0x100 && addr <= 0x118)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown TXDMA register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->txdmaregs[addr >> 2];

    trace_sungem_mmio_txdma_read(addr, val);

    return val;
}


static void sungem_realize(PCIDevice *pci_dev, Error **errp)
{
    DeviceState *dev = DEVICE(pci_dev);
    SunGEMState *s = SUNGEM(pci_dev);
    uint8_t *pci_conf;

    pci_conf = pci_dev->config;

    pci_set_word(pci_conf + PCI_STATUS,
                 PCI_STATUS_FAST_BACK |
                 PCI_STATUS_DEVSEL_MEDIUM |
                 PCI_STATUS_66MHZ);

    pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, 0x0);
    pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, 0x0);

    pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
    pci_conf[PCI_MIN_GNT] = 0x40;
    pci_conf[PCI_MAX_LAT] = 0x40;

    sungem_reset_all(s, true);
    memory_region_init(&s->sungem, OBJECT(s), "sungem", SUNGEM_MMIO_SIZE);

    memory_region_init_io(&s->greg, OBJECT(s), &sungem_mmio_greg_ops, s,
                          "sungem.greg", SUNGEM_MMIO_GREG_SIZE);
    memory_region_add_subregion(&s->sungem, 0, &s->greg);

    memory_region_init_io(&s->txdma, OBJECT(s), &sungem_mmio_txdma_ops, s,
                          "sungem.txdma", SUNGEM_MMIO_TXDMA_SIZE);
    memory_region_add_subregion(&s->sungem, 0x2000, &s->txdma);

    memory_region_init_io(&s->rxdma, OBJECT(s), &sungem_mmio_rxdma_ops, s,
                          "sungem.rxdma", SUNGEM_MMIO_RXDMA_SIZE);
    memory_region_add_subregion(&s->sungem, 0x4000, &s->rxdma);

    memory_region_init_io(&s->mac, OBJECT(s), &sungem_mmio_mac_ops, s,
                          "sungem.mac", SUNGEM_MMIO_MAC_SIZE);
    memory_region_add_subregion(&s->sungem, 0x6000, &s->mac);

    memory_region_init_io(&s->mif, OBJECT(s), &sungem_mmio_mif_ops, s,
                          "sungem.mif", SUNGEM_MMIO_MIF_SIZE);
    memory_region_add_subregion(&s->sungem, 0x6200, &s->mif);

    memory_region_init_io(&s->pcs, OBJECT(s), &sungem_mmio_pcs_ops, s,
                          "sungem.pcs", SUNGEM_MMIO_PCS_SIZE);
    memory_region_add_subregion(&s->sungem, 0x9000, &s->pcs);

    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->sungem);

    qemu_macaddr_default_if_unset(&s->conf.macaddr);
    s->nic = qemu_new_nic(&net_sungem_info, &s->conf,
                          object_get_typename(OBJECT(dev)),
                          dev->id, s);
    qemu_format_nic_info_str(qemu_get_queue(s->nic),
                             s->conf.macaddr.a);
}


static uint16_t __sungem_mii_read(SunGEMState *s, uint8_t phy_addr,
                                  uint8_t reg_addr)
{
    if (phy_addr != s->phy_addr) {
        return 0xffff;
    }
    /* Primitive emulation of a BCM5201 to please the driver,
     * ID is 0x00406210. TODO: Do a gigabit PHY like BCM5400
     */
    switch (reg_addr) {
    case MII_BMCR:
        return 0;
    case MII_PHYID1:
        return 0x0040;
    case MII_PHYID2:
        return 0x6210;
    case MII_BMSR:
        if (qemu_get_queue(s->nic)->link_down) {
            return MII_BMSR_100TX_FD  | MII_BMSR_AUTONEG;
        } else {
            return MII_BMSR_100TX_FD | MII_BMSR_AN_COMP |
                    MII_BMSR_AUTONEG | MII_BMSR_LINK_ST;
        }
    case MII_ANLPAR:
    case MII_ANAR:
        return MII_ANLPAR_TXFD;
    case 0x18: /* 5201 AUX status */
        return 3; /* 100FD */
    default:
        return 0;
    };
}


static void sungem_mmio_greg_write(void *opaque, hwaddr addr, uint64_t val,
                                   unsigned size)
{
    SunGEMState *s = opaque;

    if (!(addr < 0x20) && !(addr >= 0x1000 && addr <= 0x1010)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Write to unknown GREG register 0x%"HWADDR_PRIx"\n",
                      addr);
        return;
    }

    trace_sungem_mmio_greg_write(addr, val);

    /* Pre-write filter */
    switch (addr) {
    /* Read only registers */
    case GREG_SEBSTATE:
    case GREG_STAT:
    case GREG_STAT2:
    case GREG_PCIESTAT:
        return; /* No actual write */
    case GREG_IACK:
        val &= GREG_STAT_LATCH;
        s->gregs[GREG_STAT >> 2] &= ~val;
        sungem_eval_irq(s);
        return; /* No actual write */
    case GREG_PCIEMASK:
        val &= 0x7;
        break;
    }

    s->gregs[addr  >> 2] = val;

    /* Post write action */
    switch (addr) {
    case GREG_IMASK:
        /* Re-evaluate interrupt */
        sungem_eval_irq(s);
        break;
    case GREG_SWRST:
        switch (val & (GREG_SWRST_TXRST | GREG_SWRST_RXRST)) {
        case GREG_SWRST_RXRST:
            sungem_reset_rx(s);
            break;
        case GREG_SWRST_TXRST:
            sungem_reset_tx(s);
            break;
        case GREG_SWRST_RXRST | GREG_SWRST_TXRST:
            sungem_reset_all(s, false);
        }
        break;
    }
}


static uint64_t sungem_mmio_greg_read(void *opaque, hwaddr addr, unsigned size)
{
    SunGEMState *s = opaque;
    uint32_t val;

    if (!(addr < 0x20) && !(addr >= 0x1000 && addr <= 0x1010)) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "Read from unknown GREG register 0x%"HWADDR_PRIx"\n",
                      addr);
        return 0;
    }

    val = s->gregs[addr >> 2];

    trace_sungem_mmio_greg_read(addr, val);

    switch (addr) {
    case GREG_STAT:
        /* Side effect, clear bottom 7 bits */
        s->gregs[GREG_STAT >> 2] &= ~GREG_STAT_LATCH;
        sungem_eval_irq(s);

        /* Inject TX completion in returned value */
        val = (val & ~GREG_STAT_TXNR) |
                (s->txdmaregs[TXDMA_TXDONE >> 2] << GREG_STAT_TXNR_SHIFT);
        break;
    case GREG_STAT2:
        /* Return the status reg without side effect
         * (and inject TX completion in returned value)
         */
        val = (s->gregs[GREG_STAT >> 2] & ~GREG_STAT_TXNR) |
              (s->txdmaregs[TXDMA_TXDONE >> 2] << GREG_STAT_TXNR_SHIFT);
        break;
    }

    return val;
}


static void sungem_instance_init(Object *obj)
{
    SunGEMState *s = SUNGEM(obj);

    device_add_bootindex_property(obj, &s->conf.bootindex,
                                  "bootindex", "/ethernet-phy@0",
                                  DEVICE(obj));
}


static void sungem_reset_all(SunGEMState *s, bool pci_reset)
{
    trace_sungem_reset(pci_reset);

    sungem_reset_rx(s);
    sungem_reset_tx(s);

    s->gregs[GREG_IMASK >> 2] = 0xFFFFFFF;
    s->gregs[GREG_STAT >> 2] = 0;
    if (pci_reset) {
        uint8_t *ma = s->conf.macaddr.a;

        s->gregs[GREG_SWRST >> 2] = 0;
        s->macregs[MAC_ADDR0 >> 2] = (ma[4] << 8) | ma[5];
        s->macregs[MAC_ADDR1 >> 2] = (ma[2] << 8) | ma[3];
        s->macregs[MAC_ADDR2 >> 2] = (ma[0] << 8) | ma[1];
    } else {
        s->gregs[GREG_SWRST >> 2] &= GREG_SWRST_RSTOUT;
    }
    s->mifregs[MIF_CFG >> 2] = MIF_CFG_MDI0;
}


static void sungem_uninit(PCIDevice *dev)
{
    SunGEMState *s = SUNGEM(dev);

    qemu_del_nic(s->nic);
}


static void sungem_set_link_status(NetClientState *nc)
{
    /* We don't do anything for now as I believe none of the OSes
     * drivers use the MIF autopoll feature nor the PHY interrupt
     */
}


EXPORTED int annotate_state_write(annotate_state_t *state,
                                  const char *entry,
                                  const char *userid,
                                  const struct buf *value)
{
    return write_entry(state->mailbox, state->uid,
                       entry, userid, value, /*ignorequota*/1,
                       state->silent, NULL, /*maywrite*/1);
}

annotate: don't allow everyone to write shared server entries


static int _annotate_getdb(const char *mboxname,
                           unsigned int uid,
                           int dbflags,
                           annotate_db_t **dbp)
{
    annotate_db_t *d, *prev = NULL;
    char *fname = NULL;
    struct db *db;
    int r;

    *dbp = NULL;

    /*
     * The incoming (mboxname,uid) tuple tells us which scope we
     * need a database for.  Translate into the mboxname used to
     * key annotate_db_t's, which is slightly different: message
     * scope goes into a per-mailbox db, others in the global db.
     */
    if (!strcmpsafe(mboxname, NULL) /*server scope*/ ||
        !uid /* mailbox scope*/)
        mboxname = NULL;

    /* try to find an existing db for the mbox */
    for (d = all_dbs_head ; d ; prev = d, d = d->next) {
        if (!strcmpsafe(mboxname, d->mboxname)) {
            /* found it, bump the refcount */
            d->refcount++;
            *dbp = d;
            /*
             * Splay the db_t to the end of the global list.
             * This ensures the list remains in getdb() call
             * order, and in particular that the dbs are
             * committed in getdb() call order.  This is
             * necessary to ensure safety should a commit fail
             * while moving annotations between per-mailbox dbs
             */
            detach_db(prev, d);
            append_db(d);
            return 0;
        }
    }
    /* not found, open/create a new one */

    r = annotate_dbname(mboxname, &fname);
    if (r)
        goto error;
#if DEBUG
    syslog(LOG_ERR, "Opening annotations db %s\n", fname);
#endif

    r = cyrusdb_open(DB, fname, dbflags | CYRUSDB_CONVERT, &db);
    if (r != 0) {
        if (!(dbflags & CYRUSDB_CREATE) && r == CYRUSDB_NOTFOUND)
            goto error;
        syslog(LOG_ERR, "DBERROR: opening %s: %s",
                        fname, cyrusdb_strerror(r));
        goto error;
    }

    /* record all the above */
    d = xzmalloc(sizeof(*d));
    d->refcount = 1;
    d->mboxname = xstrdupnull(mboxname);
    d->filename = fname;
    d->db = db;

    append_db(d);

    *dbp = d;
    return 0;

error:
    free(fname);
    *dbp = NULL;
    return r;
}


EXPORTED int annotatemore_writemask(const char *mboxname, const char *entry,
                                    const char *userid, const struct buf *value)
{
    if (mboxname_userownsmailbox(userid, mboxname))
        return annotatemore_write(mboxname, entry, "", value);
    else
        return annotatemore_write(mboxname, entry, userid, value);
}


EXPORTED char *dumpentryatt(const struct entryattlist *l)
{
    struct buf buf = BUF_INITIALIZER;

    const struct entryattlist *ee;
    buf_printf(&buf, "(");
    const char *sp = "";
    const struct attvaluelist *av;
    for (ee = l ; ee ; ee = ee->next) {
        buf_printf(&buf, "%s%s (", sp, ee->entry);
        const char *insp = "";
        for (av = ee->attvalues ; av ; av = av->next) {
            buf_printf(&buf, "%s%s %s", insp, av->attrib, buf_cstring(&av->value));
            insp = " ";
        }
        buf_printf(&buf, ")");
        sp = " ";
    }
    buf_printf(&buf, ")");

    char *res = buf_release(&buf);
    buf_free(&buf);

    return res;
}


static int annotate_dbname(const char *mboxname, char **fnamep)
{
    int r = 0;
    mbentry_t *mbentry = NULL;

    if (mboxname) {
        r = mboxlist_lookup(mboxname, &mbentry, NULL);
        if (r) goto out;
    }

    r = annotate_dbname_mbentry(mbentry, fnamep);

out:
    mboxlist_entry_free(&mbentry);
    return r;
}


static int make_entry(struct buf *data,
                      const struct buf *value,
                      modseq_t modseq,
                      unsigned char flags)
{
    unsigned long l;
    static const char contenttype[] = "text/plain"; /* fake */
    unsigned long long nmodseq;

    /* Make sure that native types are wide enough */
    assert(sizeof(modseq_t) <= sizeof(unsigned long long));
    nmodseq = htonll((unsigned long long) modseq);

    l = htonl(value->len);
    buf_appendmap(data, (const char *)&l, sizeof(l));

    buf_appendmap(data, value->s ? value->s : "", value->len);
    buf_putc(data, '\0');

    /*
     * Older versions of Cyrus expected content-type and
     * modifiedsince fields after the value.  We don't support those
     * but we write out default values just in case the database
     * needs to be read by older versions of Cyrus
     */
    buf_appendcstr(data, contenttype);
    buf_putc(data, '\0');

    l = 0;  /* fake modifiedsince */
    buf_appendmap(data, (const char *)&l, sizeof(l));

    /* Append modseq at the end */
    buf_appendmap(data, (const char *)&nmodseq, sizeof(nmodseq));

    /* Append flags */
    buf_putc(data, flags);

    return 0;
}


static int _annotate_may_fetch(annotate_state_t *state,
                               const annotate_entrydesc_t *desc)
{
    unsigned int my_rights;
    unsigned int needed = 0;
    const char *acl = NULL;

    /* Admins can do anything */
    if (state->isadmin)
        return 1;

    /* Some entries need to do their own access control */
    if ((desc->type & ATTRIB_NO_FETCH_ACL_CHECK))
        return 1;

    if (state->which == ANNOTATION_SCOPE_SERVER) {
        /* RFC5464 doesn't mention access control for server
         * annotations, but this seems a sensible practice and is
         * consistent with past Cyrus behaviour */
        return 1;
    }
    else if (state->which == ANNOTATION_SCOPE_MAILBOX) {
        assert(state->mailbox || state->mbentry);

        /* Make sure its a local mailbox annotation */
        if (state->mbentry && state->mbentry->server)
            return 0;

        if (state->mailbox) acl = state->mailbox->acl;
        else if (state->mbentry) acl = state->mbentry->acl;
        /* RFC5464 is a trifle vague about access control for mailbox
         * annotations but this seems to be compliant */
        needed = ACL_LOOKUP|ACL_READ;
        /* fall through to ACL check */
    }
    else if (state->which == ANNOTATION_SCOPE_MESSAGE) {
        assert(state->mailbox);
        acl = state->mailbox->acl;
        /* RFC5257: reading from a private annotation needs 'r'.
         * Reading from a shared annotation needs 'r' */
        needed = ACL_READ;
        /* fall through to ACL check */
    }

    if (!acl)
        return 0;

    my_rights = cyrus_acl_myrights(state->auth_state, acl);

    return ((my_rights & needed) == needed);
}


EXPORTED void annotate_state_abort(annotate_state_t **statep)
{
    if (*statep)
        annotate_abort((*statep)->d);

    annotate_state_free(statep);
}


static void done_cb(void*rock __attribute__((unused)))
{
    if (annotatemore_dbopen) {
        annotatemore_close();
    }
    annotate_done();
}


static void annotation_get_freespace_total(annotate_state_t *state,
                     struct annotate_entry_list *entry)
{
    uint64_t tavail = 0;
    uint64_t ttotal = 0;
    struct buf value = BUF_INITIALIZER;

    (void) partlist_local_find_freespace_most(0, NULL, NULL, &tavail, &ttotal);
    buf_printf(&value, "%" PRIuMAX ";%" PRIuMAX, (uintmax_t)tavail, (uintmax_t)ttotal);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED int annotate_state_store(annotate_state_t *state, struct entryattlist *l)
{
    int r = 0;
    struct entryattlist *e = l;
    struct attvaluelist *av;

    annotate_state_start(state);

    /* Build a list of callbacks for storing the annotations */
    while (e) {
        int attribs;
        const annotate_entrydesc_t *desc = NULL;
        struct annotate_entry_list *nentry = NULL;

        /* See if we support this entry */
        r = find_desc_store(state, e->entry, &desc);
        if (r)
            goto cleanup;

        /* Add this entry to our list only if it
           applies to our particular server type */
        if ((desc->proxytype != PROXY_ONLY)
            || proxy_store_func)
            nentry = _annotate_state_add_entry(state, desc, e->entry);

        /* See if we are allowed to set the given attributes. */
        attribs = desc->attribs;
        av = e->attvalues;
        while (av) {
            if (!strcmp(av->attrib, "value.shared")) {
                if (!(attribs & ATTRIB_VALUE_SHARED)) {
                    r = IMAP_PERMISSION_DENIED;
                    goto cleanup;
                }
                r = annotate_canon_value(&av->value,
                                         desc->type);
                if (r)
                    goto cleanup;
                if (nentry) {
                    buf_init_ro(&nentry->shared, av->value.s, av->value.len);
                    nentry->have_shared = 1;
                }
            }
            else if (!strcmp(av->attrib, "content-type.shared") ||
                     !strcmp(av->attrib, "content-type.priv")) {
                syslog(LOG_WARNING, "annotatemore_store: client used "
                                    "deprecated attribute \"%s\", ignoring",
                                    av->attrib);
            }
            else if (!strcmp(av->attrib, "value.priv")) {
                if (!(attribs & ATTRIB_VALUE_PRIV)) {
                    r = IMAP_PERMISSION_DENIED;
                    goto cleanup;
                }
                r = annotate_canon_value(&av->value,
                                         desc->type);
                if (r)
                    goto cleanup;
                if (nentry) {
                    buf_init_ro(&nentry->priv, av->value.s, av->value.len);
                    nentry->have_priv = 1;
                }
            }
            else {
                r = IMAP_PERMISSION_DENIED;
                goto cleanup;
            }

            av = av->next;
        }

        e = e->next;
    }

    if (state->which == ANNOTATION_SCOPE_SERVER) {
        r = _annotate_store_entries(state);
    }

    else if (state->which == ANNOTATION_SCOPE_MAILBOX) {
        if (proxy_store_func) {
            r = annotate_state_need_mbentry(state);
            if (r)
                goto cleanup;
            assert(state->mbentry);
        }
        else assert(state->mailbox);

        r = _annotate_store_entries(state);
        if (r)
            goto cleanup;

        state->count++;

        if (proxy_store_func && state->mbentry->server &&
            !hash_lookup(state->mbentry->server, &state->server_table)) {
            hash_insert(state->mbentry->server, (void *)0xDEADBEEF, &state->server_table);
        }

        if (!r && !state->count) r = IMAP_MAILBOX_NONEXISTENT;

        if (proxy_store_func) {
            if (!r) {
                /* proxy command to backends */
                struct proxy_rock prock = { NULL, NULL };
                prock.mbox_pat = state->mbentry->ext_name;
                prock.entryatts = l;
                hash_enumerate(&state->server_table, store_proxy, &prock);
            }
        }
    }
    else if (state->which == ANNOTATION_SCOPE_MESSAGE) {
        r = _annotate_store_entries(state);
        if (r)
            goto cleanup;
    }

cleanup:
    annotate_state_finish(state);
    return r;
}


HIDDEN void annotate_putdb(annotate_db_t **dbp)
{
    annotate_db_t *d;

    if (!dbp || !(d = *dbp))
        return;
    assert(d->refcount > 0);
    if (--d->refcount == 0) {
        if (d->in_txn && d->txn) {
            syslog(LOG_ERR, "IOERROR: dropped last reference on "
                            "database %s with uncommitted updates, "
                            "aborting - DATA LOST!",
                            d->filename);
            annotate_abort(d);
        }
        assert(!d->in_txn);
        annotate_closedb(d);
    }
    *dbp = NULL;
}


static int annotation_set_pop3showafter(annotate_state_t *state,
                                        struct annotate_entry_list *entry,
                                        int maywrite)
{
    struct mailbox *mailbox = state->mailbox;
    int r = 0;
    time_t date;

    assert(mailbox);

    if (entry->shared.s == NULL) {
        /* Effectively removes the annotation */
        date = 0;
    }
    else {
        r = time_from_rfc5322(buf_cstring(&entry->shared), &date, DATETIME_FULL);
        if (r < 0)
            return IMAP_PROTOCOL_BAD_PARAMETERS;
    }

    if (date != mailbox->i.pop3_show_after) {
        if (!maywrite) return IMAP_PERMISSION_DENIED;
        mailbox_index_dirty(mailbox);
        mailbox_modseq_dirty(mailbox);
        mailbox->i.pop3_show_after = date;
        mboxlist_update_foldermodseq(mailbox->name, mailbox->i.highestmodseq);
    }

    return 0;
}


static int annotate_state_set_scope(annotate_state_t *state,
                                    const mbentry_t *mbentry,
                                    struct mailbox *mailbox,
                                    unsigned int uid)
{
    int r = 0;
    annotate_db_t *oldd = NULL;
    int oldwhich = state->which;

    init_internal();

    /* Carefully preserve the reference on the old DB just in case it
     * turns out to be the same as the new DB, so we avoid the overhead
     * of an unnecessary cyrusdb_open/close pair. */
    oldd = state->d;
    state->d = NULL;

    annotate_state_unset_scope(state);

    if (mbentry) {
        assert(!mailbox);
        assert(!uid);
        if (!mbentry->server) {
            /* local mailbox */
            r = mailbox_open_iwl(mbentry->name, &mailbox);
            if (r)
                goto out;
            state->ourmailbox = mailbox;
        }
        state->mbentry = mbentry;
        state->which = ANNOTATION_SCOPE_MAILBOX;
    }

    else if (uid) {
        assert(mailbox);
        state->which = ANNOTATION_SCOPE_MESSAGE;
    }
    else if (mailbox) {
        assert(!uid);
        state->which = ANNOTATION_SCOPE_MAILBOX;
    }
    else {
        assert(!mailbox);
        assert(!uid);
        state->which = ANNOTATION_SCOPE_SERVER;
    }
    assert(oldwhich == ANNOTATION_SCOPE_UNKNOWN ||
           oldwhich == state->which);
    state->mailbox = mailbox;
    state->uid = uid;

    r = _annotate_getdb(mailbox ? mailbox->name : NULL, uid,
                        CYRUSDB_CREATE, &state->d);

out:
    annotate_putdb(&oldd);
    return r;
}


static void annotation_get_mailboxopt(annotate_state_t *state,
                                      struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    uint32_t flag = (unsigned long)entry->desc->rock;
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    buf_appendcstr(&value,
                   (mailbox->i.options & flag ? "true" : "false"));
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


static void annotation_get_partition(annotate_state_t *state,
                                     struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;
    int r;

    assert(state);
    assert(state->which == ANNOTATION_SCOPE_MAILBOX);
    r = annotate_state_need_mbentry(state);
    assert(r == 0);

    /* Make sure its a local mailbox */
    if (state->mbentry->server) goto out;

    /* Check ACL */
    if (!state->isadmin &&
        (!state->mbentry->acl ||
         !(cyrus_acl_myrights(state->auth_state, state->mbentry->acl) & ACL_LOOKUP)))
        goto out;

    buf_appendcstr(&value, state->mbentry->partition);

    output_entryatt(state, entry->name, "", &value);
out:
    buf_free(&value);
}


static void annotate_closedb(annotate_db_t *d)
{
    annotate_db_t *dx, *prev = NULL;
    int r;

    /* detach from the global list */
    for (dx = all_dbs_head ; dx && dx != d ; prev = dx, dx = dx->next)
        ;
    assert(dx);
    assert(d == dx);
    detach_db(prev, d);

#if DEBUG
    syslog(LOG_ERR, "Closing annotations db %s\n", d->filename);
#endif

    r = cyrusdb_close(d->db);
    if (r)
        syslog(LOG_ERR, "DBERROR: error closing annotations %s: %s",
               d->filename, cyrusdb_strerror(r));

    free(d->filename);
    free(d->mboxname);
    memset(d, 0, sizeof(*d));   /* JIC */
    free(d);
}


static void annotation_get_freespace_percent_most(annotate_state_t *state,
                     struct annotate_entry_list *entry)
{
    uint64_t avail = 0;
    uint64_t total = 0;
    struct buf value = BUF_INITIALIZER;

    (void) partlist_local_find_freespace_most(1, &avail, &total, NULL, NULL);
    buf_printf(&value, "%" PRIuMAX ";%" PRIuMAX, (uintmax_t)avail, (uintmax_t)total);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED int annotate_state_set_server(annotate_state_t *state)
{
    return annotate_state_set_scope(state, NULL, NULL, 0);
}


static void annotation_get_usermodseq(annotate_state_t *state,
                                      struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;
    struct mboxname_counters counters;
    char *mboxname = NULL;

    memset(&counters, 0, sizeof(struct mboxname_counters));

    assert(state);
    assert(state->userid);

    mboxname = mboxname_user_mbox(state->userid, NULL);
    mboxname_read_counters(mboxname, &counters);

    buf_printf(&value, "%llu", counters.highestmodseq);

    output_entryatt(state, entry->name, state->userid, &value);
    free(mboxname);
    buf_free(&value);
}


static void annotate_begin(annotate_db_t *d)
{
    if (d)
        d->in_txn = 1;
}


static char *get_token(struct parse_state *state, const char *extra)
{
    char *token;
    char *p;

    token = tok_next(&state->tok);
    if (!token) {
        parse_error(state, "invalid annotation attributes");
        return NULL;
    }

    /* check the token */
    if (extra == NULL)
        extra = "";
    for (p = token ; *p && (isalnum(*p) || strchr(extra, *p)) ; p++)
        ;
    if (*p) {
        state->context = p;
        parse_error(state, "invalid character");
        return NULL;
    }

    state->context = token;
    return token;
}


EXPORTED void annotate_state_begin(annotate_state_t *state)
{
    init_internal();

    annotate_begin(state->d);
}


EXPORTED int annotate_state_set_mailbox(annotate_state_t *state,
                                struct mailbox *mailbox)
{
    return annotate_state_set_scope(state, NULL, mailbox, 0);
}


static void annotate_abort(annotate_db_t *d)
{
    /* don't double-abort */
    if (!d || !d->in_txn) return;

    if (d->txn) {
#if DEBUG
        syslog(LOG_ERR, "Aborting annotations db %s\n", d->filename);
#endif
        cyrusdb_abort(d->db, d->txn);
    }
    d->txn = NULL;
    d->in_txn = 0;
}


static void store_proxy(const char *server, void *data __attribute__((unused)),
                        void *rock)
{
    struct proxy_rock *prock = (struct proxy_rock *) rock;

    proxy_store_func(server, prock->mbox_pat, prock->entryatts);
}


HIDDEN int annotate_msg_cleanup(struct mailbox *mailbox, unsigned int uid)
{
    char key[MAX_MAILBOX_PATH+1];
    size_t keylen;
    int r = 0;
    annotate_db_t *d = NULL;

    assert(uid);

    r = _annotate_getdb(mailbox->name, uid, 0, &d);
    if (r) return r;

    /* must be in a transaction to modify the db */
    annotate_begin(d);

    /* If these are not true, nobody will ever commit the data we're
     * about to copy, and that would be sad */
    assert(mailbox->annot_state != NULL);
    assert(mailbox->annot_state->d == d);

    keylen = make_key(mailbox->name, uid, "", NULL, key, sizeof(key));

    r = cyrusdb_foreach(d->db, key, keylen, NULL, &cleanup_cb, d, tid(d));

    annotate_putdb(&d);
    return r;
}


EXPORTED void appendstrlistpat(struct strlist **l, char *s)
{
    struct strlist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct strlist *)xmalloc(sizeof(struct strlist));
    (*tail)->s = s;
    (*tail)->p = charset_compilepat(s);
    (*tail)->next = 0;
}


static int _annotate_store_entries(annotate_state_t *state)
{
    struct annotate_entry_list *ee;
    int r = 0;
    unsigned oldsilent = state->silent;

    /* Loop through the list of provided entries to set */
    for (ee = state->entry_list ; ee ; ee = ee->next) {
        int maystore = 1;

        /* Skip annotations that can't be stored on frontend */
        if ((ee->desc->proxytype == BACKEND_ONLY) &&
            (state->mbentry && state->mbentry->server))
            continue;

        if (ee->have_shared &&
            !_annotate_may_store(state, /*shared*/1, ee->desc)) {
            maystore = 0;
        }

        if (ee->have_priv &&
            !_annotate_may_store(state, /*shared*/0, ee->desc)) {
            maystore = 0;
        }

        r = ee->desc->set(state, ee, maystore);
        if (r)
            goto done;

        /* only the first write for message annotations isn't silent! */
        if (state->which == ANNOTATION_SCOPE_MESSAGE)
            state->silent = 1;
    }

done:
    state->silent = oldsilent;
    return r;
}


static int write_entry(struct mailbox *mailbox,
                       unsigned int uid,
                       const char *entry,
                       const char *userid,
                       const struct buf *value,
                       int ignorequota,
                       int silent,
                       const struct annotate_metadata *mdata,
                       int maywrite)

{
    char key[MAX_MAILBOX_PATH+1];
    int keylen, r;
    annotate_db_t *d = NULL;
    struct buf oldval = BUF_INITIALIZER;
    const char *mboxname = mailbox ? mailbox->name : "";
    modseq_t modseq = mdata ? mdata->modseq : 0;

    r = _annotate_getdb(mboxname, uid, CYRUSDB_CREATE, &d);
    if (r)
        return r;

    /* must be in a transaction to modify the db */
    annotate_begin(d);

    keylen = make_key(mboxname, uid, entry, userid, key, sizeof(key));

    struct annotate_metadata oldmdata;
    r = read_old_value(d, key, keylen, &oldval, &oldmdata);
    if (r) goto out;

    /* if the value is identical, don't touch the mailbox */
    if (oldval.len == value->len && (!value->len || !memcmp(oldval.s, value->s, value->len)))
        goto out;

    if (!maywrite) {
        r = IMAP_PERMISSION_DENIED;
        if (r) goto out;
    }

    if (mailbox) {
        if (!ignorequota) {
            quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_DONTCARE_INITIALIZER;
            qdiffs[QUOTA_ANNOTSTORAGE] = value->len - (quota_t)oldval.len;
            r = mailbox_quota_check(mailbox, qdiffs);
            if (r) goto out;
        }

        /* do the annot-changed here before altering the DB */
        mailbox_annot_changed(mailbox, uid, entry, userid, &oldval, value, silent);

        /* grab the message annotation modseq, if not overridden */
        if (uid && !mdata) {
            modseq = mailbox->i.highestmodseq;
        }
    }

    /* zero length annotation is deletion.
     * keep tombstones for message annotations */
    if (!value->len && !uid) {

#if DEBUG
        syslog(LOG_ERR, "write_entry: deleting key %s from %s",
                key_as_string(d, key, keylen), d->filename);
#endif

        do {
            r = cyrusdb_delete(d->db, key, keylen, tid(d), /*force*/1);
        } while (r == CYRUSDB_AGAIN);
    }
    else {
        struct buf data = BUF_INITIALIZER;
        unsigned char flags = 0;
        if (!value->len || value->s == NULL) {
            flags |= ANNOTATE_FLAG_DELETED;
        }
        else {
            // this is only here to allow cleanup of invalid values in the past...
            // the calling of this API with a NULL "userid" is bogus, because that's
            // supposed to be reserved for the make_key of prefixes - but there has
            // been API abuse in the past, so some of these are in the wild.  *sigh*.
            // Don't allow new ones to be written
            if (!userid) goto out;
        }
        make_entry(&data, value, modseq, flags);

#if DEBUG
        syslog(LOG_ERR, "write_entry: storing key %s (value: %s) to %s (modseq=" MODSEQ_FMT ")",
                key_as_string(d, key, keylen), value->s, d->filename, modseq);
#endif

        do {
            r = cyrusdb_store(d->db, key, keylen, data.s, data.len, tid(d));
        } while (r == CYRUSDB_AGAIN);
        buf_free(&data);
    }

    if (!mailbox)
        sync_log_annotation("");

out:
    annotate_putdb(&d);
    buf_free(&oldval);

    return r;
}


static void annotation_get_pop3showafter(annotate_state_t *state,
                                         struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    char valuebuf[RFC3501_DATETIME_MAX+1];
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    if (mailbox->i.pop3_show_after)
    {
        time_to_rfc3501(mailbox->i.pop3_show_after, valuebuf, sizeof(valuebuf));
        buf_appendcstr(&value, valuebuf);
    }

    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED void freestrlist(struct strlist *l)
{
    struct strlist *n;

    while (l) {
        n = l->next;
        free(l->s);
        if (l->p) charset_freepat(l->p);
        free((char *)l);
        l = n;
    }
}


EXPORTED void freeentryatts(struct entryattlist *l)
{
    struct entryattlist *n;

    while (l) {
        n = l->next;
        free(l->entry);
        freeattvalues(l->attvalues);
        free(l);
        l = n;
    }
}


EXPORTED void annotatemore_close(void)
{
    /* close all the open databases */
    while (all_dbs_head)
        annotate_closedb(all_dbs_head);

    annotatemore_dbopen = 0;
}


EXPORTED int annotatemore_findall(const char *mboxname, /* internal */
                         unsigned int uid,
                         const char *entry,
                         modseq_t since_modseq,
                         annotatemore_find_proc_t proc,
                         void *rock,
                         int flags)
{
    char key[MAX_MAILBOX_PATH+1], *p;
    size_t keylen;
    int r;
    struct find_rock frock;

    init_internal();

    assert(mboxname);
    assert(entry);
    frock.mglob = glob_init(mboxname, '.');
    frock.eglob = glob_init(entry, '/');
    frock.uid = uid;
    frock.proc = proc;
    frock.rock = rock;
    frock.since_modseq = since_modseq;
    frock.flags = flags;
    r = _annotate_getdb(mboxname, uid, 0, &frock.d);
    if (r) {
        if (r == CYRUSDB_NOTFOUND)
            r = 0;
        goto out;
    }

    /* Find fixed-string pattern prefix */
    keylen = make_key(mboxname, uid,
                      entry, NULL, key, sizeof(key));

    for (p = key; keylen; p++, keylen--) {
        if (*p == '*' || *p == '%') break;
    }
    keylen = p - key;

    r = cyrusdb_foreach(frock.d->db, key, keylen, &find_p, &find_cb,
                        &frock, tid(frock.d));

out:
    glob_free(&frock.mglob);
    glob_free(&frock.eglob);
    annotate_putdb(&frock.d);

    return r;
}


static const char *key_as_string(const annotate_db_t *d,
                                 const char *key, int keylen)
{
    const char *mboxname, *entry, *userid;
    unsigned int uid;
    int r;
    static struct buf buf = BUF_INITIALIZER;

    buf_reset(&buf);
    r = split_key(d, key, keylen, &mboxname, &uid, &entry, &userid);
    if (r)
        buf_appendcstr(&buf, "invalid");
    else
        buf_printf(&buf, "{ mboxname=\"%s\" uid=%u entry=\"%s\" userid=\"%s\" }",
                   mboxname, uid, entry, userid);
    return buf_cstring(&buf);
}


EXPORTED int annotatemore_msg_lookupmask(const char *mboxname, uint32_t uid, const char *entry,
                                         const char *userid, struct buf *value)
{
    int r = 0;
    value->len = 0; /* just in case! */

    init_internal();

    /* only if the user isn't the owner, we look for a masking value */
    if (!mboxname_userownsmailbox(userid, mboxname))
        r = annotatemore_msg_lookup(mboxname, uid, entry, userid, value);
    /* and if there isn't one, we fall through to the shared value */
    if (value->len == 0)
        r = annotatemore_msg_lookup(mboxname, uid, entry, "", value);
    /* and because of Bron's use of NULL rather than "" at FastMail... */
    if (value->len == 0)
        r = annotatemore_msg_lookup(mboxname, uid, entry, NULL, value);
    return r;
}


EXPORTED void appendentryatt(struct entryattlist **l, const char *entry,
                    struct attvaluelist *attvalues)
{
    struct entryattlist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct entryattlist *)xmalloc(sizeof(struct entryattlist));
    (*tail)->entry = xstrdup(entry);
    (*tail)->attvalues = attvalues;
    (*tail)->next = NULL;
}


static void annotation_get_fromfile(annotate_state_t *state,
                                    struct annotate_entry_list *entry)
{
    const char *filename = (const char *) entry->desc->rock;
    char path[MAX_MAILBOX_PATH+1];
    struct buf value = BUF_INITIALIZER;
    FILE *f;

    snprintf(path, sizeof(path), "%s/msg/%s", config_dir, filename);
    if ((f = fopen(path, "r")) && buf_getline(&value, f)) {

        /* TODO: we need a buf_chomp() */
        if (value.s[value.len-1] == '\r')
            buf_truncate(&value, value.len-1);
    }
    if (f) fclose(f);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


static void annotation_get_foldermodseq(annotate_state_t *state,
                                        struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;

    assert(state);
    annotate_state_need_mbentry(state);
    assert(state->mbentry);

    buf_printf(&value, "%llu", state->mbentry->foldermodseq);
    output_entryatt(state, entry->name, "", &value);

    buf_free(&value);
}


static int annotate_state_need_mbentry(annotate_state_t *state)
{
    int r = 0;

    if (!state->mbentry && state->mailbox) {
        r = mboxlist_lookup(state->mailbox->name, &state->ourmbentry, NULL);
        if (r) {
            syslog(LOG_ERR, "Failed to lookup mbentry for %s: %s",
                    state->mailbox->name, error_message(r));
            goto out;
        }
        state->mbentry = state->ourmbentry;
    }

out:
    return r;
}


static void annotate_state_start(annotate_state_t *state)
{
    /* xxx better way to determine a size for this table? */
    construct_hash_table(&state->entry_table, 100, 1);
    construct_hash_table(&state->server_table, 10, 1);
}


EXPORTED int annotatemore_rawwrite(const char *mboxname, const char *entry,
                                   const char *userid, const struct buf *value)
{
    char key[MAX_MAILBOX_PATH+1];
    int keylen, r;
    annotate_db_t *d = NULL;
    uint32_t uid = 0;

    init_internal();

    r = _annotate_getdb(mboxname, uid, CYRUSDB_CREATE, &d);
    if (r) goto done;

    /* must be in a transaction to modify the db */
    annotate_begin(d);

    keylen = make_key(mboxname, uid, entry, userid, key, sizeof(key));

    if (value->s == NULL) {
        do {
            r = cyrusdb_delete(d->db, key, keylen, tid(d), /*force*/1);
        } while (r == CYRUSDB_AGAIN);
    }
    else {
        struct buf data = BUF_INITIALIZER;

        make_entry(&data, value, uid, /*flags*/0);

        do {
            r = cyrusdb_store(d->db, key, keylen, data.s, data.len, tid(d));
        } while (r == CYRUSDB_AGAIN);
        buf_free(&data);
    }

    if (r) goto done;
    r = annotate_commit(d);

done:
    annotate_putdb(&d);

    return r;
}


static void parse_error(struct parse_state *state, const char *err)
{
    if (++state->nerrors < ANNOT_MAX_ERRORS)
    {
        struct buf msg = BUF_INITIALIZER;

        buf_printf(&msg, "%s:%u:%u:error: %s",
                   state->filename, state->lineno,
                   tok_offset(&state->tok), err);
        if (state->context && *state->context)
            buf_printf(&msg, ", at or near '%s'", state->context);
        syslog(LOG_ERR, "%s", buf_cstring(&msg));
        buf_free(&msg);
    }

    state->context = NULL;
}


EXPORTED void annotatemore_open(void)
{
    int r;
    annotate_db_t *d = NULL;

    /* force opening the global annotations db */
    r = _annotate_getdb(NULL, 0, CYRUSDB_CREATE, &d);
    if (r)
        fatal("can't open global annotations database", EX_TEMPFAIL);

    annotatemore_dbopen = 1;
}


EXPORTED void freeattvalues(struct attvaluelist *l)
{
    struct attvaluelist *n;

    while (l) {
        n = l->next;
        free(l->attrib);
        buf_free(&l->value);
        free(l);
        l = n;
    }
}


static void annotation_get_usercounters(annotate_state_t *state,
                                        struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;
    struct mboxname_counters counters;
    char *mboxname = NULL;

    assert(state);
    assert(state->userid);

    mboxname = mboxname_user_mbox(state->userid, NULL);
    int r = mboxname_read_counters(mboxname, &counters);

    if (!r) buf_printf(&value, "%u %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %u",
                       counters.version, counters.highestmodseq,
                       counters.mailmodseq, counters.caldavmodseq,
                       counters.carddavmodseq, counters.notesmodseq,
                       counters.mailfoldersmodseq, counters.caldavfoldersmodseq,
                       counters.carddavfoldersmodseq, counters.notesfoldersmodseq,
                       counters.quotamodseq, counters.raclmodseq,
                       counters.uidvalidity);

    output_entryatt(state, entry->name, state->userid, &value);
    free(mboxname);
    buf_free(&value);
}


EXPORTED void annotate_done(void)
{
    /* DB->done() handled by cyrus_done() */
    if (annotatemore_dbopen) {
        annotatemore_close();
    }
    annotate_initialized = 0;
}


static int annotation_set_tofile(annotate_state_t *state
                                    __attribute__((unused)),
                                 struct annotate_entry_list *entry,
                                 int maywrite)
{
    const char *filename = (const char *)entry->desc->rock;
    char path[MAX_MAILBOX_PATH+1];
    int r;
    FILE *f;

    if (!maywrite) return IMAP_PERMISSION_DENIED;

    snprintf(path, sizeof(path), "%s/msg/%s", config_dir, filename);

    /* XXX how do we do this atomically with other annotations? */
    if (entry->shared.s == NULL)
        return unlink(path);
    else {
        r = cyrus_mkdir(path, 0755);
        if (r)
            return r;
        f = fopen(path, "w");
        if (!f) {
            syslog(LOG_ERR, "cannot open %s for writing: %m", path);
            return IMAP_IOERROR;
        }
        fwrite(entry->shared.s, 1, entry->shared.len, f);
        fputc('\n', f);
        return fclose(f);
    }

    return IMAP_IOERROR;
}


EXPORTED void setentryatt(struct entryattlist **l, const char *entry,
                 const char *attrib, const struct buf *value)
{
    struct entryattlist *ee;

    for (ee = *l ; ee ; ee = ee->next) {
        if (!strcmp(ee->entry, entry))
            break;
    }

    if (!ee) {
        struct attvaluelist *atts = NULL;
        appendattvalue(&atts, attrib, value);
        appendentryatt(l, entry, atts);
    }
    else {
        struct attvaluelist *av;
        for (av = ee->attvalues ; av ; av = av->next) {
            if (!strcmp(av->attrib, attrib))
                break;
        }
        if (av)
            buf_copy(&av->value, value);
        else
            appendattvalue(&ee->attvalues, attrib, value);
    }
}


static void annotate_state_free(annotate_state_t **statep)
{
    annotate_state_t *state = *statep;

    if (!state)
        return;

    annotate_state_finish(state);
    annotate_state_unset_scope(state);
    free(state);
    *statep = NULL;
}


static int cleanup_cb(void *rock,
                      const char *key, size_t keylen,
                      const char *data __attribute__((unused)),
                      size_t datalen __attribute__((unused)))
{
    annotate_db_t *d = (annotate_db_t *)rock;

    return cyrusdb_delete(d->db, key, keylen, tid(d), /*force*/1);
}


EXPORTED void appendstrlist(struct strlist **l, char *s)
{
    struct strlist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct strlist *)xmalloc(sizeof(struct strlist));
    (*tail)->s = xstrdup(s);
    (*tail)->p = 0;
    (*tail)->next = 0;
}


static int normalise_attribs(struct parse_state *state, int attribs)
{
    int nattribs = 0;
    static int deprecated_warnings = 0;

    /* always provide size.shared if value.shared specified */
    if ((attribs & ATTRIB_VALUE_SHARED))
        nattribs |= ATTRIB_VALUE_SHARED|ATTRIB_SIZE_SHARED;

    /* likewise size.priv */
    if ((attribs & ATTRIB_VALUE_PRIV))
        nattribs |= ATTRIB_VALUE_PRIV|ATTRIB_SIZE_PRIV;

    /* ignore any other specified attributes */

    if ((attribs & ATTRIB_DEPRECATED)) {
        if (!deprecated_warnings++)
            parse_error(state, "deprecated attribute names such as "
                                "content-type or modified-since (ignoring)");
    }

    return nattribs;
}


EXPORTED int annotate_state_commit(annotate_state_t **statep)
{
    int r = 0;
    if (*statep)
        r = annotate_commit((*statep)->d);

    annotate_state_free(statep);
    return r;
}


static void output_entryatt(annotate_state_t *state, const char *entry,
                            const char *userid, const struct buf *value)
{
    const char *mboxname;
    char key[MAX_MAILBOX_PATH+1]; /* XXX MAX_MAILBOX_NAME + entry + userid */
    struct buf buf = BUF_INITIALIZER;

    /* We don't put any funny interpretations on NULL values for
     * some of these anymore, now that the dirty hacks are gone. */
    assert(state);
    assert(entry);
    assert(userid);
    assert(value);

    if (state->mailbox)
        mboxname = state->mailbox->name;
    else if (state->mbentry)
        mboxname = state->mbentry->name;
    else
        mboxname = "";
    /* @mboxname is now an internal mailbox name */

    /* Check if this is a new entry.
     * If so, flush our current entry.
     */
    if (state->uid != state->lastuid ||
        strcmp(mboxname, state->lastname) ||
        strcmp(entry, state->lastentry))
        flush_entryatt(state);

    strlcpy(state->lastname, mboxname, sizeof(state->lastname));
    strlcpy(state->lastentry, entry, sizeof(state->lastentry));
    state->lastuid = state->uid;

    /* check if we already returned this entry */
    strlcpy(key, mboxname, sizeof(key));
    if (state->uid) {
        char uidbuf[32];
        snprintf(uidbuf, sizeof(uidbuf), "/UID%u/", state->uid);
        strlcat(key, uidbuf, sizeof(key));
    }
    strlcat(key, entry, sizeof(key));
    strlcat(key, userid, sizeof(key));
    if (hash_lookup(key, &state->entry_table)) return;
    hash_insert(key, (void *)0xDEADBEEF, &state->entry_table);

    if (!userid[0]) { /* shared annotation */
        if ((state->attribs & ATTRIB_VALUE_SHARED)) {
            appendattvalue(&state->attvalues, "value.shared", value);
            state->found |= ATTRIB_VALUE_SHARED;
        }

        if ((state->attribs & ATTRIB_SIZE_SHARED)) {
            buf_reset(&buf);
            buf_printf(&buf, SIZE_T_FMT, value->len);
            appendattvalue(&state->attvalues, "size.shared", &buf);
            state->found |= ATTRIB_SIZE_SHARED;
        }
    }
    else { /* private annotation */
        if ((state->attribs & ATTRIB_VALUE_PRIV)) {
            appendattvalue(&state->attvalues, "value.priv", value);
            state->found |= ATTRIB_VALUE_PRIV;
        }

        if ((state->attribs & ATTRIB_SIZE_PRIV)) {
            buf_reset(&buf);
            buf_printf(&buf, SIZE_T_FMT, value->len);
            appendattvalue(&state->attvalues, "size.priv", &buf);
            state->found |= ATTRIB_SIZE_PRIV;
        }
    }
    buf_free(&buf);
}


EXPORTED int annotate_rename_mailbox(struct mailbox *oldmailbox,
                            struct mailbox *newmailbox)
{
    /* rename one mailbox */
    char *olduserid = mboxname_to_userid(oldmailbox->name);
    char *newuserid = mboxname_to_userid(newmailbox->name);
    annotate_db_t *d = NULL;
    int r = 0;

    init_internal();

    /* rewrite any per-folder annotations from the global db */
    r = _annotate_getdb(NULL, 0, /*don't create*/0, &d);
    if (r == CYRUSDB_NOTFOUND) {
        /* no global database, must not be anything to rename */
        r = 0;
        goto done;
    }
    if (r) goto done;

    annotate_begin(d);

    /* copy here - delete will dispose of old records later */
    r = _annotate_rewrite(oldmailbox, 0, olduserid,
                          newmailbox, 0, newuserid,
                         /*copy*/1);
    if (r) goto done;

    r = annotate_commit(d);
    if (r) goto done;

    /*
     * The per-folder database got moved or linked by mailbox_copy_files().
     */

 done:
    annotate_putdb(&d);
    free(olduserid);
    free(newuserid);

    return r;
}


EXPORTED void appendattvalue(struct attvaluelist **l,
                    const char *attrib,
                    const struct buf *value)
{
    struct attvaluelist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = xzmalloc(sizeof(struct attvaluelist));
    (*tail)->attrib = xstrdup(attrib);
    buf_copy(&(*tail)->value, value);
}


_annotate_state_add_entry(annotate_state_t *state,
                          const annotate_entrydesc_t *desc,
                          const char *name)
{
    struct annotate_entry_list *ee;

    ee = xzmalloc(sizeof(*ee));
    ee->name = xstrdup(name);
    ee->desc = desc;

    ee->next = state->entry_list;
    state->entry_list = ee;

    return ee;
}


EXPORTED int annotate_state_writemask(annotate_state_t *state,
                                      const char *entry,
                                      const char *userid,
                                      const struct buf *value)
{
    /* if the user is the owner, then write to the shared namespace */
    if (mboxname_userownsmailbox(userid, state->mailbox->name))
        return annotate_state_write(state, entry, "", value);
    else
        return annotate_state_write(state, entry, userid, value);
}


EXPORTED int annotate_state_writesilent(annotate_state_t *state,
                                        const char *entry,
                                        const char *userid,
                                        const struct buf *value)
{
    return write_entry(state->mailbox, state->uid,
                       entry, userid, value, /*ignorequota*/1,
                       /*silent*/1, NULL, /*maywrite*/1);
}


static int annotate_dbname_mailbox(struct mailbox *mailbox, char **fnamep)
{
    const char *conf_fname;

    if (!mailbox) return annotate_dbname_mbentry(NULL, fnamep);

    conf_fname = mailbox_meta_fname(mailbox, META_ANNOTATIONS);
    if (!conf_fname) return IMAP_MAILBOX_BADNAME;
    *fnamep = xstrdup(conf_fname);

    return 0;
}


static void annotate_state_finish(annotate_state_t *state)
{
    /* Free the entry list */
    while (state->entry_list) {
        struct annotate_entry_list *ee = state->entry_list;
        state->entry_list = ee->next;
        buf_free(&ee->shared);
        buf_free(&ee->priv);
        free(ee->name);
        free(ee);
    }

    free_hash_table(&state->entry_table, NULL);
    free_hash_table(&state->server_table, NULL);
}


static void annotation_get_freespace(annotate_state_t *state,
                                     struct annotate_entry_list *entry)
{
    uint64_t tavail = 0;
    struct buf value = BUF_INITIALIZER;

    (void) partlist_local_find_freespace_most(0, NULL, NULL, &tavail, NULL);
    buf_printf(&value, "%" PRIuMAX, (uintmax_t)tavail);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED int annotatemore_lookupmask(const char *mboxname, const char *entry,
                                     const char *userid, struct buf *value)
{
    return annotatemore_msg_lookupmask(mboxname, /*uid*/0, entry, userid, value);
}


EXPORTED void annotate_init(int (*fetch_func)(const char *, const char *,
                                     const strarray_t *, const strarray_t *),
                            int (*store_func)(const char *, const char *,
                                     struct entryattlist *))
{
    if (fetch_func) {
        proxy_fetch_func = fetch_func;
    }
    if (store_func) {
        proxy_store_func = store_func;
    }

    init_annotation_definitions();
    annotate_initialized = 1;
}


static void annotation_get_lastupdate(annotate_state_t *state,
                                      struct annotate_entry_list *entry)
{
    struct stat sbuf;
    char valuebuf[RFC3501_DATETIME_MAX+1];
    struct buf value = BUF_INITIALIZER;
    char *fname;
    int r;

    r = annotate_state_need_mbentry(state);
    if (r)
        goto out;

    fname = mbentry_metapath(state->mbentry, META_INDEX, 0);
    if (!fname)
        goto out;
    if (stat(fname, &sbuf) == -1)
        goto out;

    time_to_rfc3501(sbuf.st_mtime, valuebuf, sizeof(valuebuf));
    buf_appendcstr(&value, valuebuf);

    output_entryatt(state, entry->name, "", &value);
out:
    buf_free(&value);
}


static void annotation_get_synccrcs(annotate_state_t *state,
                                    struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    buf_printf(&value, "%u %u", mailbox->i.synccrcs.basic,
                                mailbox->i.synccrcs.annot);

    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED int annotatemore_write(const char *mboxname, const char *entry,
                                const char *userid, const struct buf *value)
{
    struct mailbox *mailbox = NULL;
    int r = 0;
    annotate_db_t *d = NULL;

    init_internal();

    r = _annotate_getdb(mboxname, /*uid*/0, CYRUSDB_CREATE, &d);
    if (r) goto done;

    if (mboxname) {
        r = mailbox_open_iwl(mboxname, &mailbox);
        if (r) goto done;
    }

    r = write_entry(mailbox, /*uid*/0, entry, userid, value,
                    /*ignorequota*/1, /*silent*/0, NULL, /*maywrite*/1);
    if (r) goto done;

    r = annotate_commit(d);

done:
    annotate_putdb(&d);
    mailbox_close(&mailbox);

    return r;
}


static void annotation_get_uniqueid(annotate_state_t *state,
                                    struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;

    assert(state->mailbox);

    if (state->mailbox->uniqueid)
        buf_appendcstr(&value, state->mailbox->uniqueid);

    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


static void detach_db(annotate_db_t *prev, annotate_db_t *d)
{
    if (prev)
        prev->next = d->next;
    else
        all_dbs_head = d->next;
    if (all_dbs_tail == d)
        all_dbs_tail = prev;
}


EXPORTED size_t sizeentryatts(const struct entryattlist *l)
{
    size_t sz = 0;
    struct attvaluelist *av;

    for ( ; l ; l = l->next)
        for (av = l->attvalues ; av ; av = av->next)
            sz += av->value.len;
    return sz;
}


EXPORTED int annotatemore_lookup(const char *mboxname, const char *entry,
                                 const char *userid, struct buf *value)
{
    return annotatemore_msg_lookup(mboxname, /*uid*/0, entry, userid, value);
}


EXPORTED void annotate_state_set_auth(annotate_state_t *state,
                             int isadmin, const char *userid,
                             const struct auth_state *auth_state)
{
    /* Note: lmtpd sometimes calls through the append code with
     * auth_state=NULL, so we cannot rely on it being non-NULL */
    state->userid = userid;
    state->isadmin = isadmin;
    state->auth_state = auth_state;
}


static void _annotate_fetch_entries(annotate_state_t *state,
                                    int proxy_check)
{
    struct annotate_entry_list *ee;

    /* Loop through the list of provided entries to get */
    for (ee = state->entry_list; ee; ee = ee->next) {

        if (proxy_check) {
            if (ee->desc->proxytype == BACKEND_ONLY &&
                proxy_fetch_func &&
                !config_getstring(IMAPOPT_PROXYSERVERS))
                continue;
        }

        if (!_annotate_may_fetch(state, ee->desc))
            continue;

        ee->desc->get(state, ee);
    }
}


EXPORTED int annotate_state_set_mailbox_mbe(annotate_state_t *state,
                                   const mbentry_t *mbentry)
{
    return annotate_state_set_scope(state, mbentry, NULL, 0);
}


static void annotation_get_annotsize(annotate_state_t *state,
                                struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    buf_printf(&value, QUOTA_T_FMT, mailbox->i.quota_annot_used);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


static void annotate_state_unset_scope(annotate_state_t *state)
{
    init_internal();

    if (state->ourmailbox)
        mailbox_close(&state->ourmailbox);
    state->mailbox = NULL;

    if (state->ourmbentry)
        mboxlist_entry_free(&state->ourmbentry);
    state->mbentry = NULL;

    state->uid = 0;
    state->which = ANNOTATION_SCOPE_UNKNOWN;
    annotate_putdb(&state->d);
}


HIDDEN int annotate_state_set_message(annotate_state_t *state,
                               struct mailbox *mailbox,
                               unsigned int uid)
{
    return annotate_state_set_scope(state, NULL, mailbox, uid);
}


static int find_desc_store(annotate_state_t *state,
                           const char *name,
                           const annotate_entrydesc_t **descp)
{
    int scope = state->which;
    const ptrarray_t *descs;
    const annotate_entrydesc_t *db_entry;
    annotate_entrydesc_t *desc;
    int i;

    if (scope == ANNOTATION_SCOPE_SERVER) {
        descs = &server_entries;
        db_entry = &server_db_entry;
    }
    else if (scope == ANNOTATION_SCOPE_MAILBOX) {
        descs = &mailbox_entries;
        db_entry = &mailbox_db_entry;
    }
    else if (scope == ANNOTATION_SCOPE_MESSAGE) {
        descs = &message_entries;
        db_entry = &message_db_entry;
    }
    else {
        syslog(LOG_ERR, "IOERROR: unknown scope in find_desc_store %d", scope);
        return IMAP_INTERNAL;
    }

    /* check for DAV annotations */
    if (state->mailbox && (state->mailbox->mbtype & MBTYPES_DAV) &&
        !strncmp(name, DAV_ANNOT_NS, strlen(DAV_ANNOT_NS))) {
        *descp = db_entry;
        return 0;
    }

    /* check known IMAP annotations */
    for (i = 0 ; i < descs->count ; i++) {
        desc = descs->data[i];
        if (strcmp(name, desc->name))
            continue;
        if (!desc->set) {
            /* read-only annotation */
            return IMAP_PERMISSION_DENIED;
        }
        *descp = desc;
        return 0;
    }

    /* unknown annotation */
    if (!config_getswitch(IMAPOPT_ANNOTATION_ALLOW_UNDEFINED))
        return IMAP_PERMISSION_DENIED;

    /* check for /flags and /vendor/cmu */
    if (scope == ANNOTATION_SCOPE_MESSAGE &&
        !strncmp(name, "/flags/", 7))
        return IMAP_PERMISSION_DENIED;

    if (!strncmp(name, IMAP_ANNOT_NS, strlen(IMAP_ANNOT_NS)))
        return IMAP_PERMISSION_DENIED;

    *descp = db_entry;
    return 0;
}


static int annotation_set_specialuse(annotate_state_t *state,
                                     struct annotate_entry_list *entry,
                                     int maywrite)
{
    struct buf res = BUF_INITIALIZER;
    int r = IMAP_PERMISSION_DENIED;

    assert(state->mailbox);

    /* Effectively removes the annotation */
    if (entry->priv.s == NULL) {
        r = write_entry(state->mailbox, state->uid, entry->name, state->userid,
                        &entry->priv, /*ignorequota*/0, /*silent*/0, NULL, maywrite);
        goto done;
    }

    r = specialuse_validate(state->mailbox->name, state->userid,
                            buf_cstring(&entry->priv), &res);
    if (r) goto done;

    r = write_entry(state->mailbox, state->uid, entry->name, state->userid,
                    &res, /*ignorequota*/0, state->silent, NULL, maywrite);

done:
    buf_free(&res);

    return r;
}


static void annotation_get_lastpop(annotate_state_t *state,
                                   struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    char valuebuf[RFC3501_DATETIME_MAX+1];
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    if (mailbox->i.pop3_last_login) {
        time_to_rfc3501(mailbox->i.pop3_last_login, valuebuf,
                        sizeof(valuebuf));
        buf_appendcstr(&value, valuebuf);
    }

    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


EXPORTED annotate_state_t *annotate_state_new(void)
{
    annotate_state_t *state;

    state = xzmalloc(sizeof(*state));
    state->which = ANNOTATION_SCOPE_UNKNOWN;

    return state;
}


static int _annotate_may_store(annotate_state_t *state,
                               int is_shared,
                               const annotate_entrydesc_t *desc)
{
    unsigned int my_rights;
    unsigned int needed = 0;
    const char *acl = NULL;

    /* Admins can do anything */
    if (state->isadmin)
        return 1;

    if (state->which == ANNOTATION_SCOPE_SERVER) {
        /* RFC5464 doesn't mention access control for server
         * annotations, but this seems a sensible practice and is
         * consistent with past Cyrus behaviour */
        return !is_shared;
    }
    else if (state->which == ANNOTATION_SCOPE_MAILBOX) {
        assert(state->mailbox);

        /* Make sure its a local mailbox annotation */
        if (state->mbentry && state->mbentry->server)
            return 0;

        acl = state->mailbox->acl;
        /* RFC5464 is a trifle vague about access control for mailbox
         * annotations but this seems to be compliant */
        needed = ACL_LOOKUP;
        if (is_shared)
            needed |= ACL_READ|ACL_WRITE|desc->extra_rights;
        /* fall through to ACL check */
    }
    else if (state->which == ANNOTATION_SCOPE_MESSAGE) {
        assert(state->mailbox);
        acl = state->mailbox->acl;
        /* RFC5257: writing to a private annotation needs 'r'.
         * Writing to a shared annotation needs 'n' */
        needed = (is_shared ? ACL_ANNOTATEMSG : ACL_READ);
        /* fall through to ACL check */
    }

    if (!acl)
        return 0;

    my_rights = cyrus_acl_myrights(state->auth_state, acl);

    return ((my_rights & needed) == needed);
}


static int annotate_canon_value(struct buf *value, int type)
{
    char *p = NULL;
    unsigned long uwhatever = 0;
    long whatever = 0;

    /* check for NIL */
    if (value->s == NULL)
        return 0;

    switch (type) {
    case ATTRIB_TYPE_STRING:
        /* free form */
        break;

    case ATTRIB_TYPE_BOOLEAN:
        /* make sure its "true" or "false" */
        if (value->len == 4 && !strncasecmp(value->s, "true", 4)) {
            buf_reset(value);
            buf_appendcstr(value, "true");
            buf_cstring(value);
        }
        else if (value->len == 5 && !strncasecmp(value->s, "false", 5)) {
            buf_reset(value);
            buf_appendcstr(value, "false");
            buf_cstring(value);
        }
        else return IMAP_ANNOTATION_BADVALUE;
        break;

    case ATTRIB_TYPE_UINT:
        /* make sure its a valid ulong ( >= 0 ) */
        errno = 0;
        buf_cstring(value);
        uwhatever = strtoul(value->s, &p, 10);
        if ((p == value->s)             /* no value */
            || (*p != '\0')             /* illegal char */
            || (unsigned)(p - value->s) != value->len
                                        /* embedded NUL */
            || errno                    /* overflow */
            || strchr(value->s, '-')) { /* negative number */
            return IMAP_ANNOTATION_BADVALUE;
        }
        break;

    case ATTRIB_TYPE_INT:
        /* make sure its a valid long */
        errno = 0;
        buf_cstring(value);
        whatever = strtol(value->s, &p, 10);
        if ((p == value->s)             /* no value */
            || (*p != '\0')             /* illegal char */
            || (unsigned)(p - value->s) != value->len
                                        /* embedded NUL */
            || errno) {                 /* underflow/overflow */
            return IMAP_ANNOTATION_BADVALUE;
        }
        break;

    default:
        /* unknown type */
        return IMAP_ANNOTATION_BADVALUE;
    }

    if (whatever || uwhatever) /* filthy compiler magic */
        return 0;

    return 0;
}


EXPORTED int annotate_delete_mailbox(struct mailbox *mailbox)
{
    int r = 0;
    char *fname = NULL;
    annotate_db_t *d = NULL;

    init_internal();

    assert(mailbox);

    /* remove any per-folder annotations from the global db */
    r = _annotate_getdb(NULL, 0, /*don't create*/0, &d);
    if (r == CYRUSDB_NOTFOUND) {
        /* no global database, must not be anything to rename */
        r = 0;
        goto out;
    }
    if (r) goto out;

    annotate_begin(d);

    r = _annotate_rewrite(mailbox,
                          /*olduid*/0, /*olduserid*/NULL,
                          /*newmailbox*/NULL,
                          /*newuid*/0, /*newuserid*/NULL,
                          /*copy*/0);
    if (r) goto out;

    /* remove the entire per-folder database */
    r = annotate_dbname_mailbox(mailbox, &fname);
    if (r) goto out;

    /* (gnb)TODO: do we even need to do this?? */
    if (unlink(fname) < 0 && errno != ENOENT) {
        syslog(LOG_ERR, "cannot unlink %s: %m", fname);
    }

    r = annotate_commit(d);

out:
    annotate_putdb(&d);
    free(fname);
    return r;
}


static void annotation_get_server(annotate_state_t *state,
                                  struct annotate_entry_list *entry)
{
    struct buf value = BUF_INITIALIZER;
    int r;

    assert(state);
    assert(state->which == ANNOTATION_SCOPE_MAILBOX);
    r = annotate_state_need_mbentry(state);
    assert(r == 0);

    /* Make sure its a remote mailbox */
    if (!state->mbentry->server) goto out;

    /* Check ACL */
    /* Note that we use a weaker form of access control than
     * normal - we only check for ACL_LOOKUP and we don't refuse
     * access if the mailbox is not local */
    if (!state->isadmin &&
        (!state->mbentry->acl ||
         !(cyrus_acl_myrights(state->auth_state, state->mbentry->acl) & ACL_LOOKUP)))
        goto out;

    buf_appendcstr(&value, state->mbentry->server);

    output_entryatt(state, entry->name, "", &value);
out:
    buf_free(&value);
}


static void init_internal()
{
    if (!annotate_initialized) {
        annotate_init(NULL, NULL);
        cyrus_modules_add(done_cb, NULL);
    }
    if (!annotatemore_dbopen) {
        annotatemore_open();
    }
}


void dupentryatt(struct entryattlist **dst,
                 const struct entryattlist *src)
{
    for ( ; src ; src = src->next) {
        struct attvaluelist *attvalues = NULL;
        dupattvalues(&attvalues, src->attvalues);
        appendentryatt(dst, src->entry, attvalues);
    }
}


static int read_old_value(annotate_db_t *d,
                          const char *key, int keylen,
                          struct buf *valp,
                          struct annotate_metadata *mdata)
{
    int r;
    size_t datalen;
    const char *data;

    do {
        r = cyrusdb_fetch(d->db, key, keylen, &data, &datalen, tid(d));
    } while (r == CYRUSDB_AGAIN);

    if (r == CYRUSDB_NOTFOUND) {
        r = 0;
        goto out;
    }
    if (r || !data)
        goto out;

    r = split_attribs(data, datalen, valp, mdata);

out:
    return r;
}


static int annotate_dbname_mbentry(const mbentry_t *mbentry,
                                   char **fnamep)
{
    const char *conf_fname;

    if (mbentry) {
        /* per-mbox database */
        conf_fname = mbentry_metapath(mbentry, META_ANNOTATIONS, /*isnew*/0);
        if (!conf_fname)
            return IMAP_MAILBOX_BADNAME;
        *fnamep = xstrdup(conf_fname);
    }
    else {
        /* global database */
        conf_fname = config_getstring(IMAPOPT_ANNOTATION_DB_PATH);

        if (conf_fname)
            *fnamep = xstrdup(conf_fname);
        else
            *fnamep = strconcat(config_dir, FNAME_GLOBALANNOTATIONS, (char *)NULL);
    }

    return 0;
}


static void annotation_get_size(annotate_state_t *state,
                                struct annotate_entry_list *entry)
{
    struct mailbox *mailbox = state->mailbox;
    struct buf value = BUF_INITIALIZER;

    assert(mailbox);

    buf_printf(&value, QUOTA_T_FMT, mailbox->i.quota_mailbox_used);
    output_entryatt(state, entry->name, "", &value);
    buf_free(&value);
}


static int annotation_set_todb(annotate_state_t *state,
                               struct annotate_entry_list *entry,
                               int maywrite)
{
    int r = 0;

    if (entry->have_shared)
        r = write_entry(state->mailbox, state->uid,
                        entry->name, "",
                        &entry->shared, 0, state->silent, NULL, maywrite);
    if (!r && entry->have_priv)
        r = write_entry(state->mailbox, state->uid,
                        entry->name, state->userid,
                        &entry->priv, 0, state->silent, NULL, maywrite);

    return r;
}


static int rw_cb(const char *mailbox __attribute__((unused)),
                 uint32_t uid __attribute__((unused)),
                 const char *entry, const char *userid,
                 const struct buf *value,
                 const struct annotate_metadata *mdata __attribute__((unused)),
                 void *rock)
{
    annotate_state_t *state = (annotate_state_t *)rock;

    if (!userid[0] || !strcmp(userid, state->userid)) {
        output_entryatt(state, entry, userid, value);
    }

    return 0;
}


static int _annotate_rewrite(struct mailbox *oldmailbox,
                             uint32_t olduid,
                             const char *olduserid,
                             struct mailbox *newmailbox,
                             uint32_t newuid,
                             const char *newuserid,
                             int copy)
{
    struct rename_rock rrock;

    rrock.oldmailbox = oldmailbox;
    rrock.newmailbox = newmailbox;
    rrock.olduserid = olduserid;
    rrock.newuserid = newuserid;
    rrock.olduid = olduid;
    rrock.newuid = newuid;
    rrock.copy = copy;

    return annotatemore_findall(oldmailbox->name, olduid, "*", /*modseq*/0,
                                &rename_cb, &rrock, /*flags*/0);
}


static void annotation_get_fromdb(annotate_state_t *state,
                                  struct annotate_entry_list *entry)
{
    const char *mboxname = (state->mailbox ? state->mailbox->name : "");
    state->found = 0;

    annotatemore_findall(mboxname, state->uid, entry->name, 0, &rw_cb, state, 0);

    if (state->found != state->attribs &&
        (!strchr(entry->name, '%') && !strchr(entry->name, '*'))) {
        /* some results not found for an explicitly specified entry,
         * make sure we emit explicit NILs */
        struct buf empty = BUF_INITIALIZER;
        if (!(state->found & (ATTRIB_VALUE_PRIV|ATTRIB_SIZE_PRIV)) &&
            (state->attribs & (ATTRIB_VALUE_PRIV|ATTRIB_SIZE_PRIV))) {
            /* store up value.priv and/or size.priv */
            output_entryatt(state, entry->name, state->userid, &empty);
        }
        if (!(state->found & (ATTRIB_VALUE_SHARED|ATTRIB_SIZE_SHARED)) &&
            (state->attribs & (ATTRIB_VALUE_SHARED|ATTRIB_SIZE_SHARED))) {
            /* store up value.shared and/or size.shared */
            output_entryatt(state, entry->name, "", &empty);
        }
        /* flush any stored attribute-value pairs */
        flush_entryatt(state);
    }
}


static int parse_table_lookup_bitmask(const struct annotate_attrib *table,
                                      struct parse_state *state)
{
    char *token = get_token(state, ".-_/ ");
    char *p;
    int i;
    int result = 0;
    tok_t tok;

    if (!token)
        return -1;
    tok_initm(&tok, token, NULL, 0);

    while ((p = tok_next(&tok))) {
        state->context = p;
        i = table_lookup(table, p);
        if (i < 0)
            return i;
        result |= i;
    }

    return result;
}


static void append_db(annotate_db_t *d)
{
    if (all_dbs_tail)
        all_dbs_tail->next = d;
    else
        all_dbs_head = d;
    all_dbs_tail = d;
    d->next = NULL;
}


EXPORTED int annotatemore_msg_lookup(const char *mboxname, uint32_t uid, const char *entry,
                                     const char *userid, struct buf *value)
{
    char key[MAX_MAILBOX_PATH+1];
    size_t keylen, datalen;
    int r;
    const char *data;
    annotate_db_t *d = NULL;
    struct annotate_metadata mdata;

    init_internal();

    r = _annotate_getdb(mboxname, uid, 0, &d);
    if (r)
        return (r == CYRUSDB_NOTFOUND ? 0 : r);

    keylen = make_key(mboxname, uid, entry, userid, key, sizeof(key));

    do {
        r = cyrusdb_fetch(d->db, key, keylen, &data, &datalen, tid(d));
    } while (r == CYRUSDB_AGAIN);

    if (!r && data) {
        r = split_attribs(data, datalen, value, &mdata);
        if (!r) {
            /* Force a copy, in case the putdb() call destroys
             * the per-db data area that @data points to.  */
            buf_cstring(value);
        }
        if (mdata.flags & ANNOTATE_FLAG_DELETED) {
            buf_free(value);
            r = CYRUSDB_NOTFOUND;
        }
    }
    if (r == CYRUSDB_NOTFOUND) r = 0;

    annotate_putdb(&d);
    return r;
}


EXPORTED int specialuse_validate(const char *mboxname, const char *userid,
                                 const char *src, struct buf *dest)
{
    const char *specialuse_extra_opt = config_getstring(IMAPOPT_SPECIALUSE_EXTRA);
    char *strval = NULL;
    strarray_t *valid = NULL;
    strarray_t *new_attribs = NULL;
    strarray_t *cur_attribs = NULL;
    struct buf mbattribs = BUF_INITIALIZER;
    int i, j;
    int r = 0;

    if (!src) {
        buf_reset(dest);
        return 0;
    }

    /* If there is a valid mboxname, we get the current specialuse annotations.
     */
    if (mboxname) {
        annotatemore_lookup(mboxname, "/specialuse", userid, &mbattribs);
        if (mbattribs.len) {
            cur_attribs = strarray_split(buf_cstring(&mbattribs), NULL, 0);
        }
    }

    /* check specialuse_extra option if set */
    if (specialuse_extra_opt)
        valid = strarray_split(specialuse_extra_opt, NULL, 0);
    else
        valid = strarray_new();

    /* strarray_add(valid, "\\All"); -- we don't support virtual folders right now */
    strarray_add(valid, "\\Archive");
    strarray_add(valid, "\\Drafts");
    /* strarray_add(valid, "\\Flagged"); -- we don't support virtual folders right now */
    strarray_add(valid, "\\Important"); // draft-ietf-specialuse-important
    strarray_add(valid, "\\Junk");
    strarray_add(valid, "\\Sent");
    strarray_add(valid, "\\Trash");
    strarray_add(valid, "\\Snoozed"); // JMAP

    new_attribs = strarray_split(src, NULL, 0);

    for (i = 0; i < new_attribs->count; i++) {
        int skip_mbcheck = 0;
        const char *item = strarray_nth(new_attribs, i);

        for (j = 0; j < valid->count; j++) { /* can't use find here */
            if (!strcasecmp(strarray_nth(valid, j), item))
                break;
            /* or without the leading '\' */
            if (!strcasecmp(strarray_nth(valid, j) + 1, item))
                break;
        }

        if (j >= valid->count) {
            r = IMAP_ANNOTATION_BADENTRY;
            goto done;
        }

        if (cur_attribs &&
            (strarray_find_case(cur_attribs, strarray_nth(valid, j), 0) >= 0)) {
            /* The mailbox has this specialuse attribute set already */
            skip_mbcheck = 1;
        }

        /* don't allow names that are already in use */
        if (!skip_mbcheck) {
            char *mbname = mboxlist_find_specialuse(strarray_nth(valid, j),
                                                    userid);
            if (mbname) {
                free(mbname);
                r = IMAP_MAILBOX_SPECIALUSE;
                goto done;
            }
        }

        /* normalise the value */
        strarray_set(new_attribs, i, strarray_nth(valid, j));
    }

    strval = strarray_join(new_attribs, " ");
    buf_setcstr(dest, strval);

done:
    free(strval);
    strarray_free(valid);
    strarray_free(new_attribs);
    strarray_free(cur_attribs);
    buf_free(&mbattribs);
    return r;
}


static void flush_entryatt(annotate_state_t *state)
{
    if (!state->attvalues)
        return;     /* nothing to flush */

    state->callback(state->lastname,
                    state->lastuid,
                    state->lastentry,
                    state->attvalues,
                    state->callback_rock);
    freeattvalues(state->attvalues);
    state->attvalues = NULL;
}


EXPORTED int annotate_state_fetch(annotate_state_t *state,
                         const strarray_t *entries, const strarray_t *attribs,
                         annotate_fetch_cb_t callback, void *rock)
{
    int i;
    struct glob *g;
    const ptrarray_t *non_db_entries;
    const annotate_entrydesc_t *db_entry;
    int r = 0;

    init_internal();

    annotate_state_start(state);
    state->callback = callback;
    state->callback_rock = rock;

    /* Build list of attributes to fetch */
    for (i = 0 ; i < attribs->count ; i++)
    {
        const char *s = attribs->data[i];
        int attribcount;

        /*
         * TODO: this is bogus.  The * and % wildcard characters applied
         * to attributes in the early drafts of the ANNOTATEMORE
         * extension, but not in later drafts where those characters are
         * actually illegal in attribute names.
         */
        g = glob_init(s, '.');

        for (attribcount = 0;
             annotation_attributes[attribcount].name;
             attribcount++) {
            if (GLOB_MATCH(g, annotation_attributes[attribcount].name)) {
                if (annotation_attributes[attribcount].entry & ATTRIB_DEPRECATED) {
                    if (strcmp(s, "*"))
                        syslog(LOG_WARNING, "annotatemore_fetch: client used "
                                            "deprecated attribute \"%s\", ignoring",
                                            annotation_attributes[attribcount].name);
                }
                else
                    state->attribs |= annotation_attributes[attribcount].entry;
            }
        }

        glob_free(&g);
    }

    if (!state->attribs)
        goto out;

    if (state->which == ANNOTATION_SCOPE_SERVER) {
        non_db_entries = &server_entries;
        db_entry = &server_db_entry;
    }
    else if (state->which == ANNOTATION_SCOPE_MAILBOX) {
        non_db_entries = &mailbox_entries;
        db_entry = &mailbox_db_entry;
    }
    else if (state->which == ANNOTATION_SCOPE_MESSAGE) {
        non_db_entries = &message_entries;
        db_entry = &message_db_entry;
    }
    else {
        syslog(LOG_ERR, "IOERROR: unknown annotation scope %d", state->which);
        r = IMAP_INTERNAL;
        goto out;
    }

    /* Build a list of callbacks for fetching the annotations */
    for (i = 0 ; i < entries->count ; i++)
    {
        const char *s = entries->data[i];
        int j;
        int check_db = 0; /* should we check the db for this entry? */

        g = glob_init(s, '/');

        for (j = 0 ; j < non_db_entries->count ; j++) {
            const annotate_entrydesc_t *desc = non_db_entries->data[j];

            if (!desc->get)
                continue;

            if (GLOB_MATCH(g, desc->name)) {
                /* Add this entry to our list only if it
                   applies to our particular server type */
                if ((desc->proxytype != PROXY_ONLY)
                    || proxy_fetch_func)
                    _annotate_state_add_entry(state, desc, desc->name);
            }

            if (!strcmp(s, desc->name)) {
                /* exact match */
                if (desc->proxytype != PROXY_ONLY) {
                    state->orig_entry = entries;  /* proxy it */
                }
                break;
            }
        }

        if (j == non_db_entries->count) {
            /* no [exact] match */
            state->orig_entry = entries;  /* proxy it */
            check_db = 1;
        }

        /* Add the db entry to our list if only if it
           applies to our particular server type */
        if (check_db &&
            ((db_entry->proxytype != PROXY_ONLY) || proxy_fetch_func)) {
            /* Add the db entry to our list */
            _annotate_state_add_entry(state, db_entry, s);
        }

        glob_free(&g);
    }

    if (state->which == ANNOTATION_SCOPE_SERVER) {
        _annotate_fetch_entries(state, /*proxy_check*/1);
    }
    else if (state->which == ANNOTATION_SCOPE_MAILBOX) {

        if (state->entry_list || proxy_fetch_func) {
            if (proxy_fetch_func) {
                r = annotate_state_need_mbentry(state);
                if (r)
                    goto out;
                assert(state->mbentry);
            }

            if (proxy_fetch_func && state->orig_entry) {
                state->orig_mailbox = state->mbentry->name;
                state->orig_attribute = attribs;
            }

            _annotate_fetch_entries(state, /*proxy_check*/1);

            if (proxy_fetch_func && state->orig_entry && state->mbentry->server &&
                !hash_lookup(state->mbentry->server, &state->server_table)) {
                /* xxx ignoring result */
                proxy_fetch_func(state->mbentry->server, state->mbentry->ext_name,
                                 state->orig_entry, state->orig_attribute);
                hash_insert(state->mbentry->server, (void *)0xDEADBEEF, &state->server_table);
            }
        }
    }
    else if (state->which == ANNOTATION_SCOPE_MESSAGE) {
        _annotate_fetch_entries(state, /*proxy_check*/0);
    }

    /* Flush last cached entry in output_entryatt() */
    flush_entryatt(state);

out:
    annotate_state_finish(state);
    return r;
}


static int find_p(void *rock, const char *key, size_t keylen,
                const char *data __attribute__((unused)),
                size_t datalen __attribute__((unused)))
{
    struct find_rock *frock = (struct find_rock *) rock;
    const char *mboxname, *entry, *userid;
    unsigned int uid;
    int r;

    r = split_key(frock->d, key, keylen, &mboxname,
                  &uid, &entry, &userid);
    if (r < 0)
        return 0;

    if (!userid)
        return 0;

    if (frock->uid &&
        frock->uid != ANNOTATE_ANY_UID &&
        frock->uid != uid)
        return 0;
    if (!GLOB_MATCH(frock->mglob, mboxname))
        return 0;
    if (!GLOB_MATCH(frock->eglob, entry))
        return 0;
    return 1;
}


EXPORTED void clearentryatt(struct entryattlist **l, const char *entry,
                   const char *attrib)
{
    struct entryattlist *ea, **pea;
    struct attvaluelist *av, **pav;

    for (pea = l ; *pea ; pea = &(*pea)->next) {
        if (!strcmp((*pea)->entry, entry))
            break;
    }
    ea = *pea;
    if (!ea)
        return; /* entry not found */

    for (pav = &(*pea)->attvalues ; *pav ; pav = &(*pav)->next) {
        if (!strcmp((*pav)->attrib, attrib))
            break;
    }
    av = *pav;
    if (!av)
        return; /* attrib not found */

    /* detach and free attvaluelist */
    *pav = av->next;
    free(av->attrib);
    buf_free(&av->value);
    free(av);

    if (!ea->attvalues) {
        /* ->attvalues is now empty, so we can detach and free *pea too */
        *pea = ea->next;
        free(ea->entry);
        free(ea);
    }
}