diff options
Diffstat (limited to 'src/irmd/main.c')
| -rw-r--r-- | src/irmd/main.c | 980 |
1 files changed, 774 insertions, 206 deletions
diff --git a/src/irmd/main.c b/src/irmd/main.c index cc15078f..daaf4129 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -40,6 +40,7 @@ #include <ouroboros/lockfile.h> #include <ouroboros/logs.h> #include <ouroboros/pthread.h> +#include <ouroboros/random.h> #include <ouroboros/rib.h> #include <ouroboros/shm_rdrbuff.h> #include <ouroboros/sockets.h> @@ -50,9 +51,11 @@ #include "irmd.h" #include "ipcp.h" +#include "oap.h" #include "reg/reg.h" #include "configfile.h" +#include <dirent.h> #include <sys/socket.h> #include <sys/un.h> #include <signal.h> @@ -71,15 +74,24 @@ #define SHM_SAN_HOLDOFF 1000 /* ms */ #define IPCP_HASH_LEN(p) hash_len((p)->dir_hash_algo) #define BIND_TIMEOUT 10 /* ms */ +#define TIMESYNC_SLACK 100 /* ms */ +#define OAP_SEEN_TIMER 20 /* s */ #define DEALLOC_TIME 300 /* s */ -#define MSGBUFSZ 2048 enum irm_state { IRMD_NULL = 0, + IRMD_INIT, IRMD_RUNNING, IRMD_SHUTDOWN }; +struct oaph { + struct list_head next; + + uint64_t stamp; + uint8_t id[OAP_ID_SIZE]; +}; + struct cmd { struct list_head next; @@ -93,6 +105,12 @@ struct { #ifdef HAVE_TOML char * cfg_file; /* configuration file path */ #endif + struct { + struct auth_ctx * ctx; /* default authentication ctx */ + struct list_head list; /* OAP headers seen before */ + pthread_mutex_t mtx; /* mutex for OAP headers */ + } auth; + struct lockfile * lf; /* single irmd per system */ struct shm_rdrbuff * rdrb; /* rdrbuff for packets */ @@ -173,8 +191,11 @@ static pid_t spawn_ipcp(struct ipcp_info * info) case IPCP_BROADCAST: exec_name = IPCP_BROADCAST_EXEC; break; - case IPCP_UDP: - exec_name = IPCP_UDP_EXEC; + case IPCP_UDP4: + exec_name = IPCP_UDP4_EXEC; + break; + case IPCP_UDP6: + exec_name = IPCP_UDP6_EXEC; break; case IPCP_ETH_LLC: exec_name = IPCP_ETH_LLC_EXEC; @@ -217,7 +238,7 @@ static pid_t spawn_ipcp(struct ipcp_info * info) } info->pid = pid; - info->state = IPCP_BOOT; + info->state = IPCP_INIT; return 0; } @@ -312,7 +333,7 @@ int bootstrap_ipcp(pid_t pid, goto fail; } - if (conf->type == IPCP_UDP) + if (conf->type == IPCP_UDP4 || conf->type == IPCP_UDP6) conf->layer_info.dir_hash_algo = (enum pol_dir_hash) HASH_MD5; if (ipcp_bootstrap(pid, conf, &layer)) { @@ -320,7 +341,7 @@ int bootstrap_ipcp(pid_t pid, goto fail; } - info.state = IPCP_BOOTSTRAPPED; + info.state = IPCP_BOOT; if (reg_set_layer_for_ipcp(&info, &layer) < 0) { log_err("Failed to set layer info for IPCP."); @@ -352,6 +373,8 @@ int enroll_ipcp(pid_t pid, goto fail; } + info.state = IPCP_BOOT; + if (reg_set_layer_for_ipcp(&info, &layer) < 0) { log_err("Failed to set layer info for IPCP."); goto fail; @@ -425,6 +448,71 @@ static int disconnect_ipcp(pid_t pid, return 0; } +static void name_update_sec_paths(struct name_info * info) +{ + char * srv_dir = OUROBOROS_SRV_CRT_DIR; + char * cli_dir = OUROBOROS_CLI_CRT_DIR; + + assert(info != NULL); + + if (strlen(info->s.enc) == 0) + sprintf(info->s.enc, "%s/%s/enc.cfg", srv_dir, info->name); + + if (strlen(info->s.crt) == 0) + sprintf(info->s.crt, "%s/%s/crt.pem", srv_dir, info->name); + + if (strlen(info->s.key) == 0) + sprintf(info->s.key, "%s/%s/key.pem", srv_dir, info->name); + + if (strlen(info->c.enc) == 0) + sprintf(info->c.enc, "%s/%s/enc.cfg", cli_dir, info->name); + + if (strlen(info->c.crt) == 0) + sprintf(info->c.crt, "%s/%s/crt.pem", cli_dir, info->name); + + if (strlen(info->c.key) == 0) + sprintf(info->c.key, "%s/%s/key.pem", cli_dir, info->name); +} + +int name_create(struct name_info * info) +{ + int ret; + + assert(info != NULL); + + name_update_sec_paths(info); + + ret = reg_create_name(info); + if (ret == -EEXIST) { + log_info("Name %s already exists.", info->name); + return 0; + } + + if (ret < 0) { + log_err("Failed to create name %s.", info->name); + return -1; + } + + log_info("Created new name: %s.", info->name); + + return 0; +} + +static int name_destroy(const char * name) +{ + + assert(name != NULL); + + if (reg_destroy_name(name) < 0) { + log_err("Failed to destroy name %s.", name); + return -1; + } + + log_info("Destroyed name: %s.", name); + + return 0; +} + int bind_program(char ** exec, const char * name, uint8_t flags) @@ -448,10 +536,8 @@ int bind_program(char ** exec, if (!reg_has_name(name)) { ni.pol_lb = LB_SPILL; strcpy(ni.name, name); - if (reg_create_name(&ni) < 0) { - log_err("Failed to create name %s.", name); + if (name_create(&ni) < 0) goto fail_name; - } } if (reg_bind_prog(name, exec, flags) < 0) { @@ -497,10 +583,8 @@ int bind_process(pid_t pid, if (!reg_has_name(name)) { ni.pol_lb = LB_SPILL; strcpy(ni.name, name); - if (reg_create_name(&ni) < 0) { - log_err("Failed to create name %s.", name); + if (name_create(&ni) < 0) goto fail; - } } if (reg_bind_proc(name, pid) < 0) { @@ -581,43 +665,6 @@ static int list_ipcps(ipcp_list_msg_t *** ipcps, return -1; } -int name_create(const struct name_info * info) -{ - int ret; - - assert(info != NULL); - - ret = reg_create_name(info); - if (ret == -EEXIST) { - log_info("Name %s already exists.", info->name); - return 0; - } - - if (ret < 0) { - log_err("Failed to create name %s.", info->name); - return -1; - } - - log_info("Created new name: %s.", info->name); - - return 0; -} - -static int name_destroy(const char * name) -{ - - assert(name != NULL); - - if (reg_destroy_name(name) < 0) { - log_err("Failed to destroy name %s.", name); - return -1; - } - - log_info("Destroyed name: %s.", name); - - return 0; -} - static int list_names(name_info_msg_t *** names, size_t * n_names) { @@ -760,67 +807,279 @@ static void __cleanup_flow(void * flow) reg_destroy_flow(((struct flow_info *) flow)->id); } +static bool file_exists(const char * path) +{ + struct stat s; + + if (stat(path, &s) < 0 && errno == ENOENT) { + log_dbg("File %s does not exist.", path); + return false; + } + + return true; +} + +static int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt, + bool * crypt) +{ + assert(paths != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + *pkp = NULL; + *crt = NULL; + + /* TODO: Allow configuration. For now, encrypt if path exists */ + *crypt = file_exists(paths->enc); + if (*crypt) + log_info("Encryption enabled for %s.", name); + + if (!file_exists(paths->crt) || !file_exists(paths->key)) { + log_info("No security info for %s.", name); + return 0; + } + + if (crypt_load_crt_file(paths->crt, crt) < 0) { + log_err("Failed to load %s for %s.", paths->crt, name); + goto fail_crt; + } + + if (crypt_load_privkey_file(paths->key, pkp) < 0) { + log_err("Failed to load %s for %s.", paths->key, name); + goto fail_key; + } + + log_info("Loaded security keys for %s.", name); + + return 0; + + fail_key: + crypt_free_crt(*crt); + *crt = NULL; + fail_crt: + return -EAUTH; +} + +static int load_srv_credentials(const char * name, + void ** pkp, + void ** crt, + bool * crypt) +{ + struct name_info info; + + assert(name != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + return -ENAME; + } + + return load_credentials(name, &info.s, pkp, crt, crypt); +} + +static int load_cli_credentials(const char * name, + void ** pkp, + void ** crt, + bool * crypt) +{ + struct name_info info; + + assert(name != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + return -ENAME; + } + + return load_credentials(name, &info.c, pkp, crt, crypt); +} + +#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0) +static int irm_check_oap_hdr(const struct oap_hdr * oap_hdr, + time_t mpl) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + struct oaph * new; + uint64_t stamp; + uint64_t cur; + uint8_t * id; + ssize_t delta; + + assert(oap_hdr != NULL); + + stamp = oap_hdr->timestamp; + id = oap_hdr->id.data; + + clock_gettime(CLOCK_REALTIME, &now); + + cur = TS_TO_UINT64(now); + + delta = (ssize_t)(cur - stamp) / MILLION; + if (delta > mpl) + log_warn("Transit time exceeds MPL by %zd ms.", delta); + if (delta < -TIMESYNC_SLACK) + log_warn("OAP header sent %zd ms from the future.", -delta); + + new = malloc(sizeof(*new)); + if (new == NULL) { + log_err("Failed to allocate memory for OAP element."); + return -ENOMEM; + } + + pthread_mutex_lock(&irmd.auth.mtx); + + list_for_each_safe(p, h, &irmd.auth.list) { + struct oaph * oaph = list_entry(p, struct oaph, next); + if (cur > oaph->stamp + OAP_SEEN_TIMER * BILLION) { + list_del(&oaph->next); + free(oaph); + continue; + } + + if (oaph->stamp == stamp && ID_IS_EQUAL(oaph->id, id)) { + log_warn("OAP header already known: " HASH_FMT64 ".", + HASH_VAL64(id)); + goto fail_replay; + } + } + + memcpy(new->id, id, OAP_ID_SIZE); + new->stamp = stamp; + + list_add_tail(&new->next, &irmd.auth.list); + + pthread_mutex_unlock(&irmd.auth.mtx); + + return 0; + + fail_replay: + pthread_mutex_unlock(&irmd.auth.mtx); + free(new); + return -EAUTH; +} + +static int irm_auth_peer(const char * name, + const struct oap_hdr * oap_hdr, + const struct oap_hdr * r_oap_hdr) +{ + void * crt; + void * pk; + buffer_t sign; + const char * n = name == NULL ? "<client>" : name; + + if (memcmp(r_oap_hdr->id.data, oap_hdr->id.data, OAP_ID_SIZE) != 0) { + log_err("OAP ID mismatch in flow allocation."); + goto fail_check; + } + + if (r_oap_hdr->crt.len == 0) { + log_info("No certificate provided by %s.", n); + return 0; + } + + if (crypt_load_crt_der(r_oap_hdr->crt, &crt) < 0) { + log_err("Failed to load certificate from %s.", n); + goto fail_check; + } + + log_dbg("Loaded peer certificate for %s.", n); + + if (name != NULL) { + if (crypt_check_crt_name(crt, n) < 0) { + log_err("Certificate does not match %s.", n); + goto fail_crt; + } + log_dbg("Certificate matches name %s.", n); + } + + if (crypt_get_pubkey_crt(crt, &pk) < 0) { + log_err("Failed to get pubkey from certificate for %s.", n); + goto fail_crt; + } + + log_dbg("Got public key from certificate for %s.", n); + + if (auth_verify_crt(irmd.auth.ctx, crt) < 0) { + log_err("Failed to verify peer %s with CA store.", n); + goto fail_crt; + } + + log_info("Successfully verified peer certificate for %s.", n); + + sign = r_oap_hdr->hdr; + sign.len -= (r_oap_hdr->sig.len + sizeof(uint16_t)); + + if (auth_verify_sig(pk, sign, r_oap_hdr->sig) < 0) { + log_err("Failed to verify signature for peer %s.", n); + goto fail_check_sig; + } + + crypt_free_key(pk); + crypt_free_crt(crt); + + log_info("Successfully authenticated %s.", n); + + return 0; + + fail_check_sig: + crypt_free_key(pk); + fail_crt: + crypt_free_crt(crt); + fail_check: + return -1; +} + static int flow_accept(struct flow_info * flow, + buffer_t * symmkey, buffer_t * data, struct timespec * abstime) { - uint8_t buf[MSGBUFSZ]; - buffer_t lpk; /* local public key */ - buffer_t rpk; /* remote public key */ - void * pkp; /* my public/private key pair */ - ssize_t key_len; - uint8_t * s; - int err; + struct oap_hdr oap_hdr; /* incoming request */ + struct oap_hdr r_oap_hdr; /* outgoing response */ + uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */ + buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */ + char name[NAME_SIZE + 1]; /* name for flow */ + void * pkp = NULL; /* signing private key */ + void * crt = NULL; /* signing certificate */ + int err; + bool crypt; /* piggyback of user data not yet implemented */ - assert(data != NULL && data->len == 0 && data->data == NULL); + assert(data != NULL && BUF_IS_EMPTY(data)); + assert(symmkey != NULL && BUF_IS_EMPTY(symmkey)); if (!reg_has_proc(flow->n_pid)) { log_err("Unknown process %d calling accept.", flow->n_pid); err = -EINVAL; - goto fail; - } - - s = malloc(SYMMKEYSZ); - if (s == NULL) { - log_err("Failed to malloc symmkey."); - err = -ENOMEM; - goto fail; - } - - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - log_err("Failed to generate key pair."); - err = -ECRYPT; - goto fail_pkp; + goto fail_flow; } - lpk.data = buf; - lpk.len = (size_t) key_len; - - log_dbg("Generated ephemeral keys for %d.", flow->n_pid); - if (reg_create_flow(flow) < 0) { log_err("Failed to create flow."); err = -EBADF; goto fail_flow; } - if (reg_prepare_flow_accept(flow, &lpk) < 0) { + if (reg_prepare_flow_accept(flow) < 0) { log_err("Failed to prepare accept."); err = -EBADF; goto fail_wait; } pthread_cleanup_push(__cleanup_flow, flow); - pthread_cleanup_push(__cleanup_pkp, pkp); - pthread_cleanup_push(free, s); - err = reg_wait_flow_accepted(flow, &rpk, abstime); + err = reg_wait_flow_accepted(flow, &oap_hdr.hdr, abstime); pthread_cleanup_pop(false); - pthread_cleanup_pop(false); - pthread_cleanup_pop(false); if (err == -ETIMEDOUT) { log_err("Flow accept timed out."); @@ -835,45 +1094,135 @@ static int flow_accept(struct flow_info * flow, assert(err == 0); - if (flow->qs.cypher_s != 0) { /* crypto requested */ - if (crypt_dh_derive(pkp, rpk, s) < 0) { + if (reg_get_name_for_flow_id(name, flow->id) < 0) { + log_err("Failed to get name for flow %d.", flow->id); + err = -EIPCP; + goto fail_cred; + } + + log_dbg("IPCP %d accepting flow %d for %s.", + flow->n_pid, flow->id, name); + + if (load_srv_credentials(name, &pkp, &crt, &crypt) < 0) { + log_err("Failed to load security keys for %s.", name); + err = -EAUTH; + goto fail_cred; + } + + if (oap_hdr_decode(oap_hdr.hdr, &oap_hdr) < 0) { + log_err("Failed to decode OAP header from %s.", name); + err = -EIPCP; + goto fail_oap_hdr; + } +#ifdef DEBUG_PROTO_OAP + debug_oap_hdr_rcv(&oap_hdr); +#endif + if (irm_check_oap_hdr(&oap_hdr, flow->mpl) < 0) { + log_err("OAP header failed replay check."); + goto fail_oap_hdr; + } + + if (crypt && oap_hdr.eph.len == 0) { + log_warn("Encryption required but no key provided."); + err = -ECRYPT; + goto fail_oap_hdr; + } + + if (oap_hdr.eph.len > 0) { /* crypto requested */ + uint8_t * s; /* symmetric encryption key */ + ssize_t key_len; /* length of local pubkey */ + void * pkp = NULL; /* ephemeral private key pair */ + + s = malloc(SYMMKEYSZ); + if (s == NULL) { + log_err("Failed to malloc symmkey."); + err = -ENOMEM; + goto fail_keys; + } + + key_len = crypt_dh_pkp_create(&pkp, buf); + if (key_len < 0) { + free(s); + log_err("Failed to generate key pair."); + err = -ECRYPT; + goto fail_keys; + } + + lpk.data = buf; + lpk.len = (size_t) key_len; + + log_dbg("Generated ephemeral keys for %d.", flow->n_pid); + + if (crypt_dh_derive(pkp, oap_hdr.eph, s) < 0) { log_err("Failed to derive secret for %d.", flow->id); + crypt_dh_pkp_destroy(pkp); + free(s); err = -ECRYPT; goto fail_derive; } - freebuf(rpk); - data->data = s; - data->len = SYMMKEYSZ; - s= NULL; - } else { - clrbuf(lpk); + + symmkey->data = s; + symmkey->len = SYMMKEYSZ; + + crypt_dh_pkp_destroy(pkp); } - if (ipcp_flow_alloc_resp(flow, 0, lpk) < 0) { + if (oap_hdr_init(oap_hdr.id, pkp, crt, lpk, *data, &r_oap_hdr) < 0) { + log_err("Failed to create OAP header."); + err = -ENOMEM; + goto fail_r_oap_hdr; + } + + if (irm_auth_peer(NULL, &r_oap_hdr, &oap_hdr) < 0) { + log_err("Failed to auth %s client, flow %d.", name, flow->id); + err = -EAUTH; + goto fail_r_oap_hdr; + } + + crypt_free_crt(crt); + crypt_free_key(pkp); + +#ifdef DEBUG_PROTO_OAP + debug_oap_hdr_snd(&r_oap_hdr); +#endif + if (ipcp_flow_alloc_resp(flow, 0, r_oap_hdr.hdr) < 0) { log_err("Failed to respond to flow allocation."); - err = -EIPCP; - goto fail_alloc_resp; + goto fail_resp; } - crypt_dh_pkp_destroy(pkp); - free(s); + log_info("Flow %d accepted by %d for %s.", + flow->id, flow->n_pid, name); + + oap_hdr_fini(&oap_hdr); + oap_hdr_fini(&r_oap_hdr); return 0; + fail_r_oap_hdr: + freebuf(*symmkey); fail_derive: - freebuf(rpk); clrbuf(lpk); + fail_keys: + oap_hdr_fini(&oap_hdr); + fail_oap_hdr: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_cred: + assert(lpk.data == NULL && lpk.len == 0); ipcp_flow_alloc_resp(flow, err, lpk); - fail_alloc_resp: - flow->state = FLOW_NULL; fail_wait: reg_destroy_flow(flow->id); fail_flow: - crypt_dh_pkp_destroy(pkp); - fail_pkp: - free(s); - fail: return err; + + fail_resp: + flow->state = FLOW_NULL; + oap_hdr_fini(&r_oap_hdr); + freebuf(*symmkey); + clrbuf(lpk); + oap_hdr_fini(&oap_hdr); + reg_destroy_flow(flow->id); + return -EIPCP; } static int flow_join(struct flow_info * flow, @@ -883,7 +1232,7 @@ static int flow_join(struct flow_info * flow, struct ipcp_info ipcp; struct layer_info layer; buffer_t hash; - buffer_t pbuf = {NULL, 0}; /* nothing to piggyback */ + buffer_t pbuf = BUF_INIT; /* nothing to piggyback */ int err; log_info("Allocating flow for %d to %s.", flow->n_pid, dst); @@ -901,6 +1250,8 @@ static int flow_join(struct flow_info * flow, goto fail_ipcp; } + flow->n_1_pid = ipcp.pid; + hash.len = hash_len((enum hash_algo) layer.dir_hash_algo); hash.data = malloc(hash.len); if (hash.data == NULL) { @@ -909,6 +1260,8 @@ static int flow_join(struct flow_info * flow, goto fail_ipcp; } + str_hash((enum hash_algo) layer.dir_hash_algo, hash.data, dst); + reg_prepare_flow_alloc(flow); if (ipcp_flow_join(flow, hash)) { @@ -936,6 +1289,7 @@ static int flow_join(struct flow_info * flow, goto fail_alloc; } + assert(pbuf.data == NULL && pbuf.len == 0); assert(err == 0); freebuf(hash); @@ -1009,23 +1363,46 @@ static int get_ipcp_by_dst(const char * dst, static int flow_alloc(struct flow_info * flow, const char * dst, + buffer_t * symmkey, buffer_t * data, struct timespec * abstime) { - uint8_t buf[MSGBUFSZ]; - buffer_t lpk ={NULL, 0}; /* local public key */ - buffer_t rpk; /* remote public key */ - void * pkp = NULL; /* my public/private key pair */ - uint8_t * s = NULL; - buffer_t hash; - int err; + struct oap_hdr oap_hdr; /* outgoing request */ + struct oap_hdr r_oap_hdr; /* incoming response */ + uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */ + buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */ + void * pkp = NULL; /* ephemeral private key pair */ + uint8_t * s = NULL; /* symmetric key */ + void * cpkp = NULL; /* signing private key */ + void * ccrt = NULL; /* signing certificate */ + buffer_t hash; + uint8_t idbuf[OAP_ID_SIZE]; + buffer_t id; + int err; + bool crypt; + /* piggyback of user data not yet implemented */ - assert(data != NULL && data->len == 0 && data->data == NULL); + assert(data != NULL && BUF_IS_EMPTY(data)); + assert(symmkey != NULL && BUF_IS_EMPTY(symmkey)); log_info("Allocating flow for %d to %s.", flow->n_pid, dst); + if (random_buffer(idbuf, OAP_ID_SIZE) < 0) { + log_err("Failed to generate ID."); + err = -EIRMD; + goto fail_id; + } + + id.data = idbuf; + id.len = OAP_ID_SIZE; - if (flow->qs.cypher_s > 0) { + if (load_cli_credentials(dst, &cpkp, &ccrt, &crypt) < 0) { + log_err("Failed to load security keys for %s.", dst); + err = -EAUTH; + goto fail_cred; + } + + if (crypt > 0) { ssize_t key_len; s = malloc(SYMMKEYSZ); @@ -1048,6 +1425,14 @@ static int flow_alloc(struct flow_info * flow, log_dbg("Generated ephemeral keys for %d.", flow->n_pid); } + if (oap_hdr_init(id, cpkp, ccrt, lpk, *data, &oap_hdr) < 0) { + log_err("Failed to create OAP header."); + err = -ENOMEM; + goto fail_oap_hdr; + } +#ifdef DEBUG_PROTO_OAP + debug_oap_hdr_snd(&oap_hdr); +#endif if (reg_create_flow(flow) < 0) { log_err("Failed to create flow."); err = -EBADF; @@ -1062,7 +1447,7 @@ static int flow_alloc(struct flow_info * flow, reg_prepare_flow_alloc(flow); - if (ipcp_flow_alloc(flow, hash, lpk)) { + if (ipcp_flow_alloc(flow, hash, oap_hdr.hdr)) { log_err("Flow allocation %d failed.", flow->id); err = -ENOTALLOC; goto fail_alloc; @@ -1073,7 +1458,7 @@ static int flow_alloc(struct flow_info * flow, pthread_cleanup_push(free, hash.data); pthread_cleanup_push(free, s); - err = reg_wait_flow_allocated(flow, &rpk, abstime); + err = reg_wait_flow_allocated(flow, &r_oap_hdr.hdr, abstime); pthread_cleanup_pop(false); pthread_cleanup_pop(false); @@ -1087,48 +1472,86 @@ static int flow_alloc(struct flow_info * flow, if (err == -1) { log_dbg("Flow allocation terminated."); - err = -EPIPE; + err = -EIPCP; goto fail_alloc; } - assert(err == 0); + log_dbg("Response received for flow %d to %s.", flow->id, dst); + + if (err < 0) { + log_warn("Flow allocation rejected for %s: %d.", dst, err); + goto fail_alloc; + } - if (flow->qs.cypher_s != 0) { /* crypto requested */ - if (crypt_dh_derive(pkp, rpk, s) < 0) { + if (oap_hdr_decode(r_oap_hdr.hdr, &r_oap_hdr) < 0) { + log_err("Failed to decode OAP header."); + err = -EIPCP; + goto fail_r_oap_hdr; + } +#ifdef DEBUG_PROTO_OAP + debug_oap_hdr_rcv(&r_oap_hdr); +#endif + if (irm_check_oap_hdr(&r_oap_hdr, flow->mpl) < 0) { + log_err("OAP header failed replay check."); + err = -EAUTH; + goto fail_r_oap_hdr; + } + + if (irm_auth_peer(dst, &oap_hdr, &r_oap_hdr) < 0) { + log_err("Failed to authenticate %s (flow %d).", dst, flow->id); + err = -EAUTH; + goto fail_r_oap_hdr; + } + + if (lpk.len > 0) { /* crypto requested */ + if (crypt_dh_derive(pkp, r_oap_hdr.eph, s) < 0) { log_err("Failed to derive secret for %d.", flow->id); err = -ECRYPT; - goto fail_derive; + goto fail_r_oap_hdr; } crypt_dh_pkp_destroy(pkp); - freebuf(rpk); - data->data = s; - data->len = SYMMKEYSZ; + + symmkey->data = s; + symmkey->len = SYMMKEYSZ; s = NULL; } + oap_hdr_fini(&r_oap_hdr); + oap_hdr_fini(&oap_hdr); + + crypt_free_crt(ccrt); + crypt_free_key(cpkp); + + /* TODO: piggyback user data if needed */ + freebuf(hash); free(s); return 0; - fail_derive: - freebuf(rpk); + fail_r_oap_hdr: flow->state = FLOW_DEALLOCATED; + oap_hdr_fini(&r_oap_hdr); fail_alloc: freebuf(hash); fail_ipcp: reg_destroy_flow(flow->id); fail_flow: - if (flow->qs.cypher_s > 0) - crypt_dh_pkp_destroy(pkp); + oap_hdr_fini(&oap_hdr); + fail_oap_hdr: + crypt_dh_pkp_destroy(pkp); fail_pkp: free(s); fail_malloc: + crypt_free_crt(ccrt); + crypt_free_key(cpkp); + fail_cred: + clrbuf(id); + fail_id: return err; } -static int wait_for_accept(enum hash_algo algo, - const uint8_t * hash) +static int wait_for_accept(const char * name) { struct timespec timeo = TIMESPEC_INIT_MS(IRMD_REQ_ARR_TIMEOUT); struct timespec abstime; @@ -1138,25 +1561,23 @@ static int wait_for_accept(enum hash_algo algo, clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - ret = reg_wait_flow_accepting(algo, hash, &abstime); + ret = reg_wait_flow_accepting(name, &abstime); if (ret == -ETIMEDOUT) { - if (reg_get_exec(algo, hash, &exec) < 0) { - log_dbg("No program bound to " HASH_FMT32 ".", - HASH_VAL32(hash)); + if (reg_get_exec(name, &exec) < 0) { + log_dbg("No program bound for %s.", name); goto fail; } - log_info("Autostarting %s.", exec[0]); - if (spawn_program(exec) < 0) { - log_dbg("Failed to autostart " HASH_FMT32 ".", - HASH_VAL32(hash)); + log_err("Failed to start %s for %s.", exec[0], name); goto fail_spawn; } + log_info("Starting %s for %s.", exec[0], name); + ts_add(&abstime, &timeo, &abstime); - ret = reg_wait_flow_accepting(algo, hash, &abstime); + ret = reg_wait_flow_accepting(name, &abstime); if (ret == -ETIMEDOUT) goto fail_spawn; @@ -1179,10 +1600,11 @@ static int flow_req_arr(struct flow_info * flow, struct layer_info layer; enum hash_algo algo; int ret; + char name[NAME_SIZE + 1]; info.pid = flow->n_1_pid; - log_info("Flow req arrived from IPCP %d for " HASH_FMT32 ".", + log_dbg("Flow req arrived from IPCP %d for " HASH_FMT32 ".", info.pid, HASH_VAL32(hash)); if (reg_get_ipcp(&info, &layer) < 0) { @@ -1193,10 +1615,17 @@ static int flow_req_arr(struct flow_info * flow, algo = (enum hash_algo) layer.dir_hash_algo; - ret = wait_for_accept(algo, hash); + if (reg_get_name_for_hash(name, algo, hash) < 0) { + log_warn("No name for " HASH_FMT32 ".", HASH_VAL32(hash)); + ret = -ENAME; + goto fail; + } + + log_info("Flow request arrived for %s.", name); + + ret = wait_for_accept(name); if (ret < 0) { - log_err("No activeprocess for " HASH_FMT32 ".", - HASH_VAL32(hash)); + log_err("No active process for %s.", name); goto fail; } @@ -1218,9 +1647,9 @@ static int flow_alloc_reply(struct flow_info * flow, int response, buffer_t * data) { - flow->state = response ? FLOW_DEALLOCATED : FLOW_ALLOCATED; + flow->state = response != 0 ? FLOW_DEALLOCATED : FLOW_ALLOCATED; - if (reg_respond_alloc(flow, data) < 0) { + if (reg_respond_alloc(flow, data, response) < 0) { log_err("Failed to reply to flow %d.", flow->id); flow->state = FLOW_DEALLOCATED; return -EBADF; @@ -1308,7 +1737,7 @@ static void * acceptloop(void * o) return (void *) 0; } -static void free_msg(void * o) +static void __cleanup_irm_msg(void * o) { irm_msg__free_unpacked((irm_msg_t *) o, NULL); } @@ -1327,6 +1756,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) int res; irm_msg_t * ret_msg; buffer_t data; + buffer_t symmkey = BUF_INIT;; memset(&flow, 0, sizeof(flow)); @@ -1351,7 +1781,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) ret_msg->code = IRM_MSG_CODE__IRM_REPLY; - pthread_cleanup_push(free_msg, ret_msg); + pthread_cleanup_push(__cleanup_irm_msg, ret_msg); switch (msg->code) { case IRM_MSG_CODE__IRM_CREATE_IPCP: @@ -1380,7 +1810,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = disconnect_ipcp(msg->pid, msg->dst, msg->comp); break; case IRM_MSG_CODE__IRM_BIND_PROGRAM: - /* Make exec NULL terminated instead of empty string terminated */ + /* Terminate with NULL instead of "" */ free(msg->exec[msg->n_exec - 1]); msg->exec[msg->n_exec - 1] = NULL; res = bind_program(msg->exec, msg->name, msg->opts); @@ -1406,8 +1836,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = list_ipcps(&ret_msg->ipcps, &ret_msg->n_ipcps); break; case IRM_MSG_CODE__IRM_CREATE_NAME: - strcpy(name.name, msg->names[0]->name); - name.pol_lb = msg->names[0]->pol_lb; + name = name_info_msg_to_s(msg->name_info); res = name_create(&name); break; case IRM_MSG_CODE__IRM_DESTROY_NAME: @@ -1423,17 +1852,21 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = name_unreg(msg->name, msg->pid); break; case IRM_MSG_CODE__IRM_FLOW_ACCEPT: + tpm_wait_work(irmd.tpm); data.len = msg->pk.len; data.data = msg->pk.data; msg->has_pk = false; assert(data.len > 0 ? data.data != NULL : data.data == NULL); flow = flow_info_msg_to_s(msg->flow_info); - res = flow_accept(&flow, &data, abstime); + res = flow_accept(&flow, &symmkey, &data, abstime); if (res == 0) { ret_msg->flow_info = flow_info_s_to_msg(&flow); - ret_msg->has_symmkey = data.len != 0; - ret_msg->symmkey.data = data.data; - ret_msg->symmkey.len = data.len; + ret_msg->has_symmkey = symmkey.len != 0; + ret_msg->symmkey.data = symmkey.data; + ret_msg->symmkey.len = symmkey.len; + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; } break; case IRM_MSG_CODE__IRM_FLOW_ALLOC: @@ -1443,12 +1876,15 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) assert(data.len > 0 ? data.data != NULL : data.data == NULL); flow = flow_info_msg_to_s(msg->flow_info); abstime = abstime == NULL ? &max : abstime; - res = flow_alloc(&flow, msg->dst, &data, abstime); + res = flow_alloc(&flow, msg->dst, &symmkey, &data, abstime); if (res == 0) { ret_msg->flow_info = flow_info_s_to_msg(&flow); - ret_msg->has_symmkey = data.len != 0; - ret_msg->symmkey.data = data.data; - ret_msg->symmkey.len = data.len; + ret_msg->has_symmkey = symmkey.len != 0; + ret_msg->symmkey.data = symmkey.data; + ret_msg->symmkey.len = symmkey.len; + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; } break; case IRM_MSG_CODE__IRM_FLOW_JOIN: @@ -1457,7 +1893,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) abstime = abstime == NULL ? &max : abstime; res = flow_join(&flow, msg->dst, abstime); if (res == 0) - ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->flow_info = flow_info_s_to_msg(&flow); break; case IRM_MSG_CODE__IRM_FLOW_DEALLOC: flow = flow_info_msg_to_s(msg->flow_info); @@ -1540,10 +1976,10 @@ static void * mainloop(void * o) continue; } - tpm_dec(irmd.tpm); + tpm_begin_work(irmd.tpm); pthread_cleanup_push(__cleanup_close_ptr, &sfd); - pthread_cleanup_push(free_msg, msg); + pthread_cleanup_push(__cleanup_irm_msg, msg); ret_msg = do_command_msg(msg); @@ -1556,12 +1992,12 @@ static void * mainloop(void * o) } if (ret_msg->result == -EPIPE) { - log_dbg("Terminated command: application closed socket."); + log_dbg("Terminated command: remote closed socket."); goto fail; } if (ret_msg->result == -EIRMD) { - log_dbg("Terminated command: IRMd not in running state."); + log_dbg("Terminated command: IRMd not running."); goto fail; } @@ -1596,54 +2032,20 @@ static void * mainloop(void * o) pthread_cleanup_pop(true); pthread_cleanup_pop(true); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; fail: irm_msg__free_unpacked(ret_msg, NULL); fail_msg: close(sfd); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; } return (void *) 0; } -static void irm_fini(void) -{ -#ifdef HAVE_FUSE - struct timespec wait = TIMESPEC_INIT_MS(1); - int retries = 5; -#endif - if (irmd_get_state() != IRMD_NULL) - log_warn("Unsafe destroy."); - - tpm_destroy(irmd.tpm); - - close(irmd.sockfd); - - if (unlink(IRM_SOCK_PATH)) - log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); - - if (irmd.rdrb != NULL) - shm_rdrbuff_destroy(irmd.rdrb); - - if (irmd.lf != NULL) - lockfile_destroy(irmd.lf); - - pthread_mutex_destroy(&irmd.cmd_lock); - pthread_cond_destroy(&irmd.cmd_cond); - pthread_rwlock_destroy(&irmd.state_lock); - -#ifdef HAVE_FUSE - while (rmdir(FUSE_PREFIX) < 0 && retries-- > 0) - nanosleep(&wait, NULL); - if (retries < 0) - log_err("Failed to remove " FUSE_PREFIX); -#endif -} - #ifdef HAVE_FUSE static void destroy_mount(char * mnt) { @@ -1719,6 +2121,79 @@ void * irm_sanitize(void * o) return (void *) 0; } +static int irm_load_store(char * dpath) +{ + struct stat st; + struct dirent * dent; + DIR * dir; + void * crt; + + if (stat(dpath, &st) == -1) { + log_dbg("Store directory %s not found.", dpath); + return 0; + } + + if (!S_ISDIR(st.st_mode)) { + log_err("%s is not a directory.", dpath); + goto fail_dir; + } + + /* loop through files in directory and load certificates */ + dir = opendir(dpath); + if (dir == NULL) { + log_err("Failed to open %s.", dpath); + goto fail_dir; + } + + while ((dent = readdir(dir)) != NULL) { + char path[NAME_PATH_SIZE + 1]; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", dpath, + dent->d_name); + + if (stat(path, &st) == -1) { + log_dbg("Failed to stat %s.", path); + continue; + } + + if (!S_ISREG(st.st_mode)) { + log_dbg("%s is not a regular file.", path); + goto fail_file; + } + + if (crypt_load_crt_file(path, &crt) < 0) { + log_err("Failed to load certificate from %s.", path); + goto fail_file; + } + + if (auth_add_crt_to_store(irmd.auth.ctx, crt) < 0) { + log_err("Failed to add certificate from %s to store.", + path); + goto fail_crt_add; + } + + log_dbg("Loaded certificate: %s.", path); + + crypt_free_crt(crt); + } + + closedir(dir); + + log_info("Loaded certificates from %s.", dpath); + + return 0; + + fail_crt_add: + crypt_free_crt(crt); + fail_file: + closedir(dir); + fail_dir: + return -1; +} static int irm_init(void) { @@ -1819,6 +2294,30 @@ static int irm_init(void) log_err("Failed to greate thread pool."); goto fail_tpm_create; } + + if (pthread_mutex_init(&irmd.auth.mtx, NULL) < 0) { + log_err("Failed to initialize auth mutex."); + goto fail_auth_mtx; + } + + irmd.auth.ctx = auth_create_ctx(); + if (irmd.auth.ctx == NULL) { + log_err("Failed to create auth store context."); + goto fail_auth_ctx; + } + + list_head_init(&irmd.auth.list); + + if (irm_load_store(OUROBOROS_CA_CRT_DIR) < 0) { + log_err("Failed to load CA certificates."); + goto fail_auth_ctx; + } + + if (irm_load_store(OUROBOROS_CHAIN_DIR) < 0) { + log_err("Failed to load intermediate certificates."); + goto fail_auth_ctx; + } + #ifdef HAVE_FUSE mask = umask(0); @@ -1844,6 +2343,8 @@ static int irm_init(void) gcry_control(GCRYCTL_INITIALIZATION_FINISHED); #endif + irmd_set_state(IRMD_INIT); + return 0; #ifdef HAVE_LIBGCRYPT @@ -1851,8 +2352,12 @@ static int irm_init(void) #ifdef HAVE_FUSE rmdir(FUSE_PREFIX); #endif - tpm_destroy(irmd.tpm); + auth_destroy_ctx(irmd.auth.ctx); #endif + fail_auth_ctx: + pthread_mutex_destroy(&irmd.auth.mtx); + fail_auth_mtx: + tpm_destroy(irmd.tpm); fail_tpm_create: shm_rdrbuff_destroy(irmd.rdrb); fail_rdrbuff: @@ -1872,6 +2377,69 @@ static int irm_init(void) return -1; } +static void irm_fini(void) +{ + struct list_head * p; + struct list_head * h; +#ifdef HAVE_FUSE + struct timespec wait = TIMESPEC_INIT_MS(1); + int retries = 5; +#endif + if (irmd_get_state() != IRMD_INIT) + log_warn("Unsafe destroy."); + + pthread_mutex_lock(&irmd.auth.mtx); + + list_for_each_safe(p, h, &irmd.auth.list) { + struct oaph * oaph = list_entry(p, struct oaph, next); + list_del(&oaph->next); + free(oaph); + } + + pthread_mutex_unlock(&irmd.auth.mtx); + pthread_mutex_destroy(&irmd.auth.mtx); + + auth_destroy_ctx(irmd.auth.ctx); + + tpm_destroy(irmd.tpm); + + close(irmd.sockfd); + + if (unlink(IRM_SOCK_PATH)) + log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); + + if (irmd.rdrb != NULL) + shm_rdrbuff_destroy(irmd.rdrb); + + if (irmd.lf != NULL) + lockfile_destroy(irmd.lf); + + pthread_mutex_lock(&irmd.cmd_lock); + + list_for_each_safe(p, h, &irmd.cmds) { + struct cmd * cmd = list_entry(p, struct cmd, next); + list_del(&cmd->next); + close(cmd->fd); + free(cmd); + } + + pthread_mutex_unlock(&irmd.cmd_lock); + + pthread_mutex_destroy(&irmd.cmd_lock); + pthread_cond_destroy(&irmd.cmd_cond); + pthread_rwlock_destroy(&irmd.state_lock); + +#ifdef HAVE_FUSE + while (rmdir(FUSE_PREFIX) < 0 && retries-- > 0) + nanosleep(&wait, NULL); + if (retries < 0) + log_err("Failed to remove " FUSE_PREFIX); +#endif + assert(list_is_empty(&irmd.cmds)); + + irmd.state = IRMD_NULL; +} + static void usage(void) { printf("Usage: irmd \n" @@ -1885,11 +2453,11 @@ static void usage(void) static int irm_start(void) { + irmd_set_state(IRMD_RUNNING); + if (tpm_start(irmd.tpm)) goto fail_tpm_start; - irmd_set_state(IRMD_RUNNING); - if (pthread_create(&irmd.irm_sanitize, NULL, irm_sanitize, NULL)) goto fail_irm_sanitize; @@ -1904,9 +2472,9 @@ static int irm_start(void) pthread_cancel(irmd.irm_sanitize); pthread_join(irmd.irm_sanitize, NULL); fail_irm_sanitize: - irmd_set_state(IRMD_NULL); tpm_stop(irmd.tpm); fail_tpm_start: + irmd_set_state(IRMD_INIT); return -1; } @@ -1947,7 +2515,7 @@ static void irm_stop(void) tpm_stop(irmd.tpm); - irmd_set_state(IRMD_NULL); + irmd_set_state(IRMD_INIT); } static void irm_argparse(int argc, @@ -1998,8 +2566,8 @@ static void * kill_dash_nine(void * o) slept += intv; } - log_dbg("I am become Death, destroyer of hung processes."); - + log_dbg("I guess I’ll have to shut you down for good this time,"); + log_dbg("already tried a SIGQUIT, so now it’s KILL DASH 9."); #ifdef IRMD_KILL_ALL_PROCESSES reg_kill_all_proc(SIGKILL); nanosleep(&ts, NULL); @@ -2058,7 +2626,7 @@ int main(int argc, if (geteuid() != 0) { printf("IPC Resource Manager must be run as root.\n"); - exit(EXIT_FAILURE); + goto fail_irm_init; } if (irm_init() < 0) |
