/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef mod_md_md_store_h
#define mod_md_md_store_h

struct apr_array_header_t;
struct md_cert_t;
struct md_pkey_t;
struct md_pkey_spec_t;

const char *md_store_group_name(unsigned int group);

typedef struct md_store_t md_store_t;

/**
 * A store for domain related data.
 *
 * The Key for a piece of data is the set of 3 items
 *   <group> + <domain> + <aspect>
 *
 * Examples:
 * "domains" + "greenbytes.de" + "pubcert.pem"
 * "ocsp" + "greenbytes.de" + "ocsp-XXXXX.json"
 *
 * Storage groups are pre-defined, domain and aspect names can be freely chosen.
 *
 * Groups reflect use cases and come with security restrictions. The groups 
 * DOMAINS, ARCHIVE and NONE are only accessible during the startup 
 * phase of httpd.
 *
 * Private key are stored unencrypted only in restricted groups. Meaning that certificate
 * keys in group DOMAINS are not encrypted, but only readable at httpd start/reload.
 * Keys in unrestricted groups are encrypted using a pass phrase generated once and stored
 * in NONE.
 */

/** Value types handled by a store */
typedef enum {
    MD_SV_TEXT,         /* plain text, value is (char*) */
    MD_SV_JSON,         /* JSON serialization, value is (md_json_t*) */
    MD_SV_CERT,         /* PEM x509 certificate, value is (md_cert_t*) */
    MD_SV_PKEY,         /* PEM private key, value is (md_pkey_t*) */
    MD_SV_CHAIN,        /* list of PEM x509 certificates, value is 
                           (apr_array_header_t*) of (md_cert*) */
} md_store_vtype_t;

/** Store storage groups */
typedef enum {
    MD_SG_NONE,         /* top level of store, name MUST be NULL in calls */
    MD_SG_ACCOUNTS,     /* ACME accounts */
    MD_SG_CHALLENGES,   /* challenge response data for a domain */ 
    MD_SG_DOMAINS,      /* live certificates and settings for a domain */
    MD_SG_STAGING,      /* staged set of certificate and settings, maybe incomplete */
    MD_SG_ARCHIVE,      /* Archived live sets of a domain */
    MD_SG_TMP,          /* temporary domain storage */
    MD_SG_OCSP,         /* OCSP stapling related domain data */
    MD_SG_COUNT,        /* number of storage groups, used in setups */
} md_store_group_t;

#define MD_FN_MD                "md.json"
#define MD_FN_JOB               "job.json"
#define MD_FN_HTTPD_JSON        "httpd.json"

/* The corresponding names for current cert & key files are constructed
 * in md_store and md_crypt.
 */

/* These three legacy filenames are only used in md_store_fs to
 * upgrade 1.0 directories.  They should not be used for any other
 * purpose.
 */
#define MD_FN_PRIVKEY           "privkey.pem"
#define MD_FN_PUBCERT           "pubcert.pem"
#define MD_FN_CERT              "cert.pem"

/**
 * Load the JSON value at key "group/name/aspect", allocated from pool p.
 * @return APR_ENOENT if there is no such value
 */
apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group, 
                                const char *name, const char *aspect, 
                                struct md_json_t **pdata, apr_pool_t *p);
/**
 * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
 * already is a value for this key.
 */
apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                                const char *name, const char *aspect, 
                                struct md_json_t *data, int create);

/**
 * Load the value of type at key "group/name/aspect", allocated from pool p. Usually, the
 * type is expected to be the same as used in saving the value. Some conversions will work,
 * others will fail the format.
 * @return APR_ENOENT if there is no such value
 */
apr_status_t md_store_load(md_store_t *store, md_store_group_t group, 
                           const char *name, const char *aspect, 
                           md_store_vtype_t vtype, void **pdata, 
                           apr_pool_t *p);
/**
 * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
 * already is a value for this key. The provided data MUST be of the correct type.
 */
apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                           const char *name, const char *aspect, 
                           md_store_vtype_t vtype, void *data, 
                           int create);

/**
 * Remove the value stored at key "group/name/aspect". Unless force != 0, a missing
 * value will cause the call to fail with APR_ENOENT.
 */ 
apr_status_t md_store_remove(md_store_t *store, md_store_group_t group, 
                             const char *name, const char *aspect, 
                             apr_pool_t *p, int force);
/**
 * Remove everything matching key "group/name".
 */ 
apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p, 
                            md_store_group_t group, const char *name);

/**
 * Remove all items matching the name/aspect patterns that have not been
 * modified since the given timestamp.
 */
apr_status_t md_store_remove_not_modified_since(md_store_t *store, apr_pool_t *p, 
                                                apr_time_t modified,
                                                md_store_group_t group, 
                                                const char *name, 
                                                const char *aspect);

/**
 * inspect callback function. Invoked for each matched value. Values allocated from
 * ptemp may disappear any time after the call returned. If this function returns
 * 0, the iteration is aborted. 
 */
typedef int md_store_inspect(void *baton, const char *name, const char *aspect, 
                             md_store_vtype_t vtype, void *value, apr_pool_t *ptemp);

/**
 * Iterator over all existing values matching the name pattern. Patterns are evaluated
 * using apr_fnmatch() without flags.
 */
apr_status_t md_store_iter(md_store_inspect *inspect, void *baton, md_store_t *store, 
                           apr_pool_t *p, md_store_group_t group, const char *pattern, 
                           const char *aspect, md_store_vtype_t vtype);

/**
 * Move everything matching key "from/name" from one group to another. If archive != 0,
 * move any existing "to/name" into a new "archive/new_name" location.
 */
apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
                           md_store_group_t from, md_store_group_t to,
                           const char *name, int archive);

/**
 * Rename a group member.
 */
apr_status_t md_store_rename(md_store_t *store, apr_pool_t *p,
                             md_store_group_t group, const char *name, const char *to);

/**
 * Get the filename of an item stored in "group/name/aspect". The item does
 * not have to exist.
 */
apr_status_t md_store_get_fname(const char **pfname, 
                                md_store_t *store, md_store_group_t group, 
                                const char *name, const char *aspect, 
                                apr_pool_t *p);

/**
 * Make a compare on the modification time of "group1/name/aspect" vs. "group2/name/aspect".
 */
int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                      const char *name, const char *aspect, apr_pool_t *p);

/**
 * Iterate over all names that exist in a group, e.g. there are items matching
 * "group/pattern". The inspect function is called with the name and NULL aspect
 * and value.
 */
apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
                                 apr_pool_t *p, md_store_group_t group, const char *pattern);

/**
 * Get the modification time of the item store under "group/name/aspect".
 * @return modification time or 0 if the item does not exist.
 */
apr_time_t md_store_get_modified(md_store_t *store, md_store_group_t group,  
                                 const char *name, const char *aspect, apr_pool_t *p);

/**
 * Acquire a cooperative, global lock on store modifications.

 * This will only prevent other children/processes/cluster nodes from
 * doing the same and does not protect individual store functions from
 * being called without it.
 * @param store the store
 * @param p memory pool to use
 * @param max_wait maximum time to wait in order to acquire
 * @return APR_SUCCESS when lock was obtained
 */
apr_status_t md_store_lock_global(md_store_t *store, apr_pool_t *p, apr_time_t max_wait);

/**
 * Realease the global store lock. Will do nothing if there is no lock.
 */
void md_store_unlock_global(md_store_t *store, apr_pool_t *p);

/**************************************************************************************************/
/* Storage handling utils */

int md_exists(md_store_t *store, md_store_group_t group,
              const char *name, apr_pool_t *p);
apr_status_t md_load(md_store_t *store, md_store_group_t group,
                     const char *name, md_t **pmd, apr_pool_t *p);
apr_status_t md_save(struct md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                     md_t *md, int create);
apr_status_t md_remove(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                     const char *name, int force);

int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                const char *name, apr_pool_t *p);

typedef int md_store_md_inspect(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp);

apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store, 
                              apr_pool_t *p, md_store_group_t group, const char *pattern);


const char *md_pkey_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);
const char *md_chain_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);

apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, 
                          const char *name, struct md_pkey_spec_t *spec,
                          struct md_pkey_t **ppkey, apr_pool_t *p);
apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                          const char *name, struct md_pkey_spec_t *spec,
                          struct md_pkey_t *pkey, int create);

apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
                             struct md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert, 
                             apr_pool_t *p);
apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, 
                             md_store_group_t group, const char *name, 
                             struct md_pkey_spec_t *spec, 
                             struct apr_array_header_t *pubcert, int create);

/**************************************************************************************************/
/* X509 complete credentials */

typedef struct md_credentials_t md_credentials_t;
struct md_credentials_t {
    struct md_pkey_spec_t *spec;
    struct md_pkey_t *pkey;
    struct apr_array_header_t *chain;
};

apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name, 
                           struct md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p);
apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                           const char *name, md_credentials_t *creds, int create);

/**************************************************************************************************/
/* implementation interface */

typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group, 
                                      const char *name, const char *aspect, 
                                      md_store_vtype_t vtype, void **pvalue, 
                                      apr_pool_t *p);
typedef apr_status_t md_store_save_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                                      const char *name, const char *aspect, 
                                      md_store_vtype_t vtype, void *value, 
                                      int create);
typedef apr_status_t md_store_remove_cb(md_store_t *store, md_store_group_t group, 
                                        const char *name, const char *aspect,  
                                        apr_pool_t *p, int force);
typedef apr_status_t md_store_purge_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                                        const char *name);

typedef apr_status_t md_store_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store, 
                                      apr_pool_t *p, md_store_group_t group, const char *pattern,
                                      const char *aspect, md_store_vtype_t vtype);

typedef apr_status_t md_store_names_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store, 
                                            apr_pool_t *p, md_store_group_t group, const char *pattern);

typedef apr_status_t md_store_move_cb(md_store_t *store, apr_pool_t *p, md_store_group_t from, 
                                      md_store_group_t to, const char *name, int archive);

typedef apr_status_t md_store_rename_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                                        const char *from, const char *to);

typedef apr_status_t md_store_get_fname_cb(const char **pfname, 
                                           md_store_t *store, md_store_group_t group, 
                                           const char *name, const char *aspect, 
                                           apr_pool_t *p);

typedef int md_store_is_newer_cb(md_store_t *store, 
                                 md_store_group_t group1, md_store_group_t group2,  
                                 const char *name, const char *aspect, apr_pool_t *p);

typedef apr_time_t md_store_get_modified_cb(md_store_t *store, md_store_group_t group,  
                                            const char *name, const char *aspect, apr_pool_t *p);

typedef apr_status_t md_store_remove_nms_cb(md_store_t *store, apr_pool_t *p, 
                                            apr_time_t modified, md_store_group_t group, 
                                            const char *name, const char *aspect);
typedef apr_status_t md_store_lock_global_cb(md_store_t *store, apr_pool_t *p, apr_time_t max_wait);
typedef void md_store_unlock_global_cb(md_store_t *store, apr_pool_t *p);

struct md_store_t {
    md_store_save_cb *save;
    md_store_load_cb *load;
    md_store_remove_cb *remove;
    md_store_move_cb *move;
    md_store_rename_cb *rename;
    md_store_iter_cb *iterate;
    md_store_names_iter_cb *iterate_names;
    md_store_purge_cb *purge;
    md_store_get_fname_cb *get_fname;
    md_store_is_newer_cb *is_newer;
    md_store_get_modified_cb *get_modified;
    md_store_remove_nms_cb *remove_nms;
    md_store_lock_global_cb *lock_global;
    md_store_unlock_global_cb *unlock_global;
};


#endif /* mod_md_md_store_h */
