// This file is under GNU General Public License 3.0 // see LICENSE.txt // generate state machine code // Copyleft (c) 2016-2020, p≡p foundation // Written by Volker Birk include yslt.yml2 tstylesheet { include standardlib.ysl2 include ./functions.ysl2 template "/protocol" { if "count(fsm/state)>0" document "generated/{@name}_event.h", "text" || /** * @file «@name»_event.h * @brief Structures and functions for «@name» events. * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #ifndef «yml:ucase(@name)»_EVENT_H #define «yml:ucase(@name)»_EVENT_H #include "pEpEngine.h" #ifdef __cplusplus extern "C" { #endif /** * «@name» is also defined as «yml:ucase(@name)» for ASN.1 reasons and * forward declaration to avoid the need for header inclusion everywhere. * @see struct «@name» in «@name».h */ typedef struct «@name» «yml:ucase(@name)»; /** forward declaration - @see enum «@name»_PR in «@name».h */ typedef int «yml:ucase(@name)»_PR; /** * Data for an event related to a message */ typedef struct «@name»_event { // state machine data «yml:ucase(@name)»_PR fsm; /*!< state machine type associated with this event */ int event; /*!< type of event */ «yml:ucase(@name)» *msg; /*!< the «@name» messaged caused by (or causing???) this event */ // transport data pEp_identity *from; /*!< identity of the message sender */ char *sender_fpr; /*!< fpr of key used by the message sender */ identity_list *own_identities; /*!< List of our own identities */ } «@name»_event_t; /** * * * @brief allocate a new «@name»_event * * @param[in] fsm finite state machine the event is for * @param[in] event event or None * @param[in] msg message to compute event from * * @retval pointer to new event * @retval NULL in case of failure * */ DYNAMIC_API «@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «yml:ucase(@name)» *msg); #define «yml:ucase(@name)»_TIMEOUT_EVENT new_«@name»_event(«@name»_PR_NOTHING, 0, NULL); /** * * * @brief free memory occupied by event * * @param[in] ev event to free * */ DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev); #ifdef __cplusplus } #endif #endif || if "count(fsm/state)>0" document "generated/{@name}_event.c", "text" || /** * @file «@name»_event.c * @brief Allocation, fsm drivers, and handling for «@name» events. * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #include "platform.h" #include "pEp_internal.h" #include "«@name»_event.h" #include "«@name»_func.h" `` for "fsm" | #include "«@name»_fsm.h" DYNAMIC_API «@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «@name»_t *msg) { «@name»_event_t *ev = («@name»_event_t *) calloc(1, sizeof(«@name»_event_t)); assert(ev); if (!ev) return NULL; ev->fsm = fsm; ev->event = event; ev->msg = msg; if (msg) { switch (fsm) { `` apply "fsm", 3, mode=event default: // unknown protocol free(ev); return NULL; } } return ev; } DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev) { if (ev) { free_identity_list(ev->own_identities); free_«@name»_message(ev->msg); free_identity(ev->from); free(ev->sender_fpr); free(ev); } } || if "count(fsm/state)>0" document "generated/{@name}_impl.h", "text" { || /** * @file «@name»_impl.h * @brief «@name» protocol implementation declarations * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #ifndef «yml:ucase(@name)»_IMPL_H #define «yml:ucase(@name)»_IMPL_H #include "fsm_common.h" #include "«@name»_event.h" #include "message_api.h" #include "../asn.1/«@name».h" #define «yml:ucase(@name)»_THRESHOLD «@threshold» `` for "fsm[count(state)>0]" | #define «yml:ucase(@name)»_THRESHOLD «@threshold» #ifdef __cplusplus extern "C" { #endif ///////////////////////////////////////////////////////////////////// // conditions ///////////////////////////////////////////////////////////////////// || for "func:distinctName(*//condition)" { | /** | * @brief Evaluate condition: Is «@name» true? | * @param[in] session the session | * @param[out] result true if «@name», else false | * @retval status | */ | PEP_STATUS «@name»(PEP_SESSION session, bool * result); } || ///////////////////////////////////////////////////////////////////// // actions ///////////////////////////////////////////////////////////////////// || for "func:distinctName(*//action)" { | /** | * @brief Action: Do «@name» | * @param[in] session the session | * @retval status | */ | PEP_STATUS «@name»(PEP_SESSION session); } || ///////////////////////////////////////////////////////////////////// // timeout handlers ///////////////////////////////////////////////////////////////////// || for "fsm[@threshold > 0]" { | /** | * | * | * @brief Handle timeouts for «@name» state machine | * | * @param[in] session the session | * @retval status | */ | PEP_STATUS «@name»TimeoutHandler(PEP_SESSION session); } || ///////////////////////////////////////////////////////////////////// // «@name» state machine driver, message sending, and event receipt ///////////////////////////////////////////////////////////////////// /** * * * @brief send message about «@name» event to communication partners using state * * @param[in] session the session * @param[in] fsm the finite state machine from which to get state * @param[in] message_type type of message to send * * @retval status of message send * */ PEP_STATUS send_«@name»_message( PEP_SESSION session, «@name»_PR fsm, int message_type ); /** * * * @brief receive «@name» message and store it in state * * @param[in] session the session * @param[in] ev the event to process * * @retval status of event processing/storage * */ PEP_STATUS recv_«@name»_event( PEP_SESSION session, «@name»_event_t *ev ); /** * * * @brief «@name» state machine driver * * @param[in] session the session * @param[in] fsm finite state machine to drive * @param[in] ev the event to process??? * * @retval ??? * * @note if fsm or event set to 0 use fields in src if present * */ PEP_STATUS «@name»_driver( PEP_SESSION session, «@name»_PR fsm, int event ); ///////////////////////////////////////////////////////////////////////////// // API used by the engine internally // ///////////////////////////////////////////////////////////////////////////// /** * * * @brief Internal engine API: call this if you need to signal an external «@name» event * * @param[in] session the session * @param[in] fsm finite state machine * @param[in] event event type * @param[out] own_identities list of own identities * * @retval status * * @ownership the ownership of own_identities goes to the callee * */ PEP_STATUS signal_«@name»_event( PEP_SESSION session, «@name»_PR fsm, int event, identity_list *own_identities ); /** * * * @brief Internal engine API: to be called by transports receiving a «@name» message * * @param[in] session the session * @param[in] rating rating of the decrypted «@name» message * @param[out] data payload of «@name» message * @param[out] from identity of the message sender * @param[out] fpr fingerprint of the sender key used to sign the message * * @retval status * */ PEP_STATUS signal_«@name»_message( PEP_SESSION session, PEP_rating rating, const char *data, size_t size, const pEp_identity *from, const char *sender_fpr ); #ifdef __cplusplus } #endif #endif || } if "count(fsm/state)>0" document "generated/{@name}_impl.c", "text" { || /** * @file «@name»_impl.c * @brief «@name» protocol implementation: driver, event handling, message signalling/sending/receipt, etc. * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #include "«@name»_impl.h" #include "pEp_internal.h" #include "«@name»_event.h" #include "«yml:lcase(@name)»_codec.h" #include "baseprotocol.h" #include "security_checks.h" `` for "fsm[count(state)>0]" | #include "«@name»_fsm.h" `` apply "fsm[count(state)>0]", 0, mode=timeout PEP_STATUS «@name»_driver( PEP_SESSION session, «@name»_PR fsm, int event ) { assert(session); if (!session) return PEP_ILLEGAL_VALUE; switch (fsm) { case None: if (!event) { // timeout occured `` for "fsm[count(state)>0]" |>>>> «../@name»_driver(session, «../@name»_PR_«yml:lcase(@name)», None); return PEP_STATUS_OK; } return PEP_ILLEGAL_VALUE; `` apply "fsm[count(state)>0]", mode=reset_state_machine; default: return PEP_ILLEGAL_VALUE; } int next_state = None; do { switch (fsm) { `` apply "fsm[count(state)>0]", 3, mode=driver default: return PEP_ILLEGAL_VALUE; } } while (next_state); return PEP_STATUS_OK; } PEP_STATUS signal_«@name»_event( PEP_SESSION session, «@name»_PR fsm, int event, identity_list *own_identities ) { «@name»_t *msg = NULL; «@name»_event_t *ev = NULL; assert(session && fsm > 0 && event > None); if (!(session && fsm > 0 && event > None)) return PEP_ILLEGAL_VALUE; PEP_STATUS status = PEP_STATUS_OK; if (!session->inject_«yml:lcase(@name)»_event) return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK; if (event < Extra) { // FIXME: there should be a mapping between event and message type // with current implementation they've got an offset of 1 msg = new_«@name»_message(fsm, event - 1); if (!msg) { status = PEP_OUT_OF_MEMORY; goto the_end; } status = update_«@name»_message(session, msg); if (status) goto the_end; } ev = new_«@name»_event(fsm, event, msg); if (!ev) { status = PEP_OUT_OF_MEMORY; goto the_end; } status = set_all_userids_to_own(session, own_identities); if (status != PEP_STATUS_OK) goto the_end; ev->own_identities = own_identities; int result = session->inject_«yml:lcase(@name)»_event(ev, session->«yml:lcase(@name)»_management); if (result) { status = PEP_STATEMACHINE_ERROR; goto the_end; } return PEP_STATUS_OK; the_end: free_«@name»_event(ev); // msg gets freed here return status; } PEP_STATUS signal_«@name»_message( PEP_SESSION session, PEP_rating rating, const char *data, size_t size, const pEp_identity *from, const char *sender_fpr ) { assert(session && data && size); if (!(session && data && size)) return PEP_ILLEGAL_VALUE; if (!session->inject_«yml:lcase(@name)»_event) return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK; PEP_STATUS status = PEP_STATUS_OK; «@name»_event_t *ev = NULL; «@name»_t *msg = NULL; status = decode_«@name»_message(data, size, &msg); if (status) return status; «@name»_PR fsm = msg->present; int event = 0; bool is_own_key = false; switch (fsm) { `` apply "fsm[count(state)>0]", 2, mode=signal_message default: status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; goto the_end; } ev = new_«@name»_event(fsm, event, msg); if (!ev) { status = PEP_OUT_OF_MEMORY; goto the_end; } // add transport data if (from) { ev->from = identity_dup(from); if (!ev->from) { status = PEP_OUT_OF_MEMORY; goto the_end; } } if (sender_fpr) { ev->sender_fpr = strdup(sender_fpr); assert(ev->sender_fpr); if (!ev->sender_fpr) { status = PEP_OUT_OF_MEMORY; goto the_end; } } int result = session->inject_«yml:lcase(@name)»_event(ev, session->«yml:lcase(@name)»_management); if (result) { status = PEP_STATEMACHINE_ERROR; goto the_end; } return PEP_STATUS_OK; the_end: free_«@name»_event(ev); // msg gets freed here return status; } PEP_STATUS send_«@name»_message( PEP_SESSION session, «@name»_PR fsm, int message_type ) { PEP_STATUS status = PEP_STATUS_OK; assert(session && (int) fsm > None && message_type > None); if (!(session && (int) fsm > None && message_type > None)) return PEP_ILLEGAL_VALUE; time_t now = time(NULL); switch (fsm) { || apply "fsm[count(state)>0]", 2, mode=send; || } «@name»_t *msg = new_«@name»_message(fsm, message_type); if (!msg) return PEP_OUT_OF_MEMORY; char *data = NULL; message *m = NULL; identity_list *channels = NULL; char *key_data = NULL; size_t key_data_size = 0; stringlist_t *extra = NULL; bool transaction; status = update_«@name»_message(session, msg); if (status) goto the_end; size_t size = 0; status = encode_«@name»_message(msg, &data, &size); if (status) goto the_end; // we never use this if (session->«yml:lcase(@name)»_state.comm_partner.identity && session->«yml:lcase(@name)»_state.comm_partner.identity->fpr) { free(session->«yml:lcase(@name)»_state.comm_partner.identity->fpr); session->«yml:lcase(@name)»_state.comm_partner.identity->fpr = NULL; } // if we have this we always use this if (session->«yml:lcase(@name)»_state.comm_partner.sender_fpr) { free(session->«yml:lcase(@name)»_state.transport.sender_fpr); session->«yml:lcase(@name)»_state.transport.sender_fpr = strdup(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr); assert(session->«yml:lcase(@name)»_state.transport.sender_fpr); if (!session->«yml:lcase(@name)»_state.transport.sender_fpr) { status = PEP_OUT_OF_MEMORY; goto the_end; } } switch (fsm) { || apply "fsm[count(state)>0]", 2, mode=send2; || default: break; } for (identity_list *li = channels; li && li->ident ; li = li->next) { message *_m = NULL; char *_data = NULL; _data = malloc(size); assert(_data); if (!_data) { status = PEP_OUT_OF_MEMORY; goto the_end; } memcpy(_data, data, size); switch (message_type) { `` for "fsm/message[@security='unencrypted' and ../@name!='KeySync']" | #error unencrypted only allowed with KeySync `` for "fsm/message[@security='unencrypted' and ../@name='KeySync']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»: status = try_base_prepare_message( session, li->ident, li->ident, BASE_SYNC, _data, size, li->ident->fpr, &_m ); if (status) { free(_data); if (status == PEP_OUT_OF_MEMORY) goto the_end; continue; } attach_own_key(session, _m); decorate_message(session, _m, PEP_rating_undefined, NULL, true, true); m = _m; break; `` for "fsm/message[@security='untrusted']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»: // add fpr of key of comm partner if (!session->«yml:lcase(@name)»_state.transport.sender_fpr) { status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } extra = new_stringlist(session->«yml:lcase(@name)»_state.transport.sender_fpr); if (!extra) { status = PEP_OUT_OF_MEMORY; goto the_end; } status = base_prepare_message( session, li->ident, li->ident, BASE_SYNC, _data, size, NULL, &_m ); if (status) { if (status == PEP_OUT_OF_MEMORY) goto the_end; free(_data); continue; } status = try_encrypt_message(session, _m, extra, &m, PEP_enc_PEP, 0); if (status) { if (status == PEP_OUT_OF_MEMORY) goto the_end; status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } add_opt_field(m, "pEp-auto-consume", "yes"); m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation"); free_message(_m); break; // attach own keys for new member `` for "fsm/message[@security='attach_own_keys_for_new_member']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»: // check if we had a former negotiation transaction = false; for (int i=0; i < session->«yml:lcase(@name)»_state.own.negotiation.size; i++) { if (session->«yml:lcase(@name)»_state.own.negotiation.buf[i]) { transaction = true; break; } } // if it is a former negotiation check if the key // is fully trusted and the sender key of this // transaction; if so add the sender key to extra // keys allowing this new partner to read the // secret keys if (transaction) { if (!(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr && session->«yml:lcase(@name)»_state.transport.from && session->«yml:lcase(@name)»_state.transport.from->user_id)) { status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } // test if this is a green channel pEp_identity *ident = new_identity(NULL, session->«yml:lcase(@name)»_state.comm_partner.sender_fpr, session->«yml:lcase(@name)»_state.transport.from->user_id, NULL ); if (!ident) { status = PEP_OUT_OF_MEMORY; goto the_end; } status = get_trust(session, ident); if (status) { free_identity(ident); goto the_end; } assert(ident->comm_type == PEP_ct_pEp); // we don't deliver otherwise if (ident->comm_type != PEP_ct_pEp) { free_identity(ident); status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } free_identity(ident); // test if we accepted this as own key already bool is_own_key = false; status = own_key_is_listed(session, session->«yml:lcase(@name)»_state.comm_partner.sender_fpr, &is_own_key); assert(!status); if (status) goto the_end; if (!is_own_key) { status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } // if so add key of comm partner to extra keys extra = new_stringlist(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr); if (!extra) { status = PEP_OUT_OF_MEMORY; goto the_end; } } status = base_prepare_message( session, li->ident, li->ident, BASE_SYNC, _data, size, NULL, &_m ); if (status) { free(_data); if (status == PEP_OUT_OF_MEMORY) goto the_end; continue; } // export secret keys into memory key_data = strdup(""); assert(key_data); if (!key_data) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data_size = 1; // N.B. If null termination makes us happy for debugging, fine, but // if we include this in the size, libetpan will null terminate and // go bananas. We can't have a NUL in the mime text. for (stringlist_t *sl = session->«yml:lcase(@name)»_state.own.keys; sl && sl->value ; sl = sl->next) { char *_key_data = NULL; size_t _size = 0; status = export_secret_key(session, sl->value, &_key_data, &_size); if (status && status != PEP_KEY_NOT_FOUND) { free(_data); free_message(_m); goto the_end; } if (status != PEP_KEY_NOT_FOUND) { assert(_key_data && _size); char *n = realloc(key_data, key_data_size + _size); if (!n) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = n; key_data_size += _size; strlcat(key_data, _key_data, key_data_size); free(_key_data); _key_data = NULL; } status = export_key(session, sl->value, &_key_data, &_size); if (status && status != PEP_KEY_NOT_FOUND) { free(_data); free_message(_m); goto the_end; } if (status != PEP_KEY_NOT_FOUND) { assert(_key_data && _size); char *n = realloc(key_data, key_data_size + _size); if (!n) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = n; key_data_size += _size; strlcat(key_data, _key_data, key_data_size); free(_key_data); _key_data = NULL; } } // add secret key data as attachment // N.B. The -1 makes sure we do NOT add a NUL into the mime stream! bloblist_t *bl = bloblist_add(_m->attachments, key_data, key_data_size - 1, "application/octet-stream", "file://own.key"); if (!bl) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = NULL; status = try_encrypt_message(session, _m, extra, &m, PEP_enc_PEP, 0); if (status) { if (status == PEP_OUT_OF_MEMORY) goto the_end; status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } add_opt_field(m, "pEp-auto-consume", "yes"); m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation"); free_message(_m); break; // attach own keys for group `` for "fsm/message[@security='attach_own_keys_for_group']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»: status = base_prepare_message( session, li->ident, li->ident, BASE_SYNC, _data, size, NULL, &_m ); if (status) { free(_data); if (status == PEP_OUT_OF_MEMORY) goto the_end; continue; } // export secret keys into memory key_data = strdup(""); assert(key_data); if (!key_data) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data_size = 1; // N.B. If null termination makes us happy for debugging, fine, but // if we include this in the size, libetpan will null terminate and // go bananas. We can't have a NUL in the mime text. for (stringlist_t *sl = session->«yml:lcase(@name)»_state.own.keys; sl && sl->value ; sl = sl->next) { char *_key_data = NULL; size_t _size = 0; status = export_secret_key(session, sl->value, &_key_data, &_size); if (status && status != PEP_KEY_NOT_FOUND) { free(_data); free_message(_m); goto the_end; } if (status != PEP_KEY_NOT_FOUND) { assert(_key_data && _size); char *n = realloc(key_data, key_data_size + _size); if (!n) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = n; key_data_size += _size; strlcat(key_data, _key_data, key_data_size); free(_key_data); _key_data = NULL; } status = export_key(session, sl->value, &_key_data, &_size); if (status && status != PEP_KEY_NOT_FOUND) { free(_data); free_message(_m); goto the_end; } if (status != PEP_KEY_NOT_FOUND) { assert(_key_data && _size); char *n = realloc(key_data, key_data_size + _size); if (!n) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = n; key_data_size += _size; strlcat(key_data, _key_data, key_data_size); free(_key_data); _key_data = NULL; } } // add secret key data as attachment // N.B. The -1 makes sure we do NOT add a NUL into the mime stream! bl = bloblist_add(_m->attachments, key_data, key_data_size - 1, "application/octet-stream", "file://own.key"); if (!bl) { free(_data); free_message(_m); status = PEP_OUT_OF_MEMORY; goto the_end; } key_data = NULL; // we do not support extra keys here and will only encrypt to ourselves status = try_encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0); if (status) { if (status == PEP_OUT_OF_MEMORY) goto the_end; status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; continue; } add_opt_field(m, "pEp-auto-consume", "yes"); m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation"); free_message(_m); break; default: // security=trusted only status = base_prepare_message( session, li->ident, li->ident, BASE_SYNC, _data, size, NULL, &_m ); if (status) { free(_data); if (status == PEP_OUT_OF_MEMORY) goto the_end; continue; } status = try_encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0); if (status) { status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT; if (status == PEP_OUT_OF_MEMORY) goto the_end; continue; } add_opt_field(m, "pEp-auto-consume", "yes"); m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation"); free_message(_m); } status = session->messageToSend(m); m = NULL; } || if "fsm/message[@ratelimit>0]" { || switch (fsm) { || for "fsm[message/@ratelimit>0]" { || case Sync_PR_«yml:lcase(@name)»: switch (message_type) { || for "message[@ratelimit>0]" || case «../@name»_PR_«yml:mixedCase(@name)»: session->«yml:lcase(../../@name)»_state.own.last_«../@name»_«@name» = now; break; || || default: break; } break; || } || default: break; } || } || the_end: free_stringlist(extra); free_identity_list(channels); free_message(m); free(data); free(key_data); free_«@name»_message(msg); if (status) SERVICE_ERROR_LOG(session, "send_«@name»_message()", status); return status; } PEP_STATUS recv_«@name»_event( PEP_SESSION session, «@name»_event_t *ev ) { assert(session && ev); if (!(session && ev)) return PEP_ILLEGAL_VALUE; PEP_STATUS status = PEP_STATUS_OK; «@name»_PR fsm = (int) None; int event = None; if (ev->event > None && ev->event < Extra) { status = update_«@name»_state(session, ev->msg, &fsm, &event); if (status) goto the_end; if (ev->fsm) { if (ev->fsm != fsm |`> |` ev->event != event) { status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; goto the_end; } } else if (ev->event) { status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; goto the_end; } } else { fsm = ev->fsm; event = ev->event; } // update transport data if (ev->from) { free_identity(session->«yml:lcase(@name)»_state.transport.from); session->«yml:lcase(@name)»_state.transport.from = ev->from; ev->from = NULL; } if (ev->sender_fpr) { free(session->«yml:lcase(@name)»_state.transport.sender_fpr); session->«yml:lcase(@name)»_state.transport.sender_fpr = ev->sender_fpr; /* Removed for temp ENGINE-647 fix. Will be reenabled once better sync debugging is in. // Check against saved comm_partner sender_fpr state, if there is one yet if (session->«yml:lcase(@name)»_state.comm_partner.sender_fpr) { // 1. Does it match sender_fpr? if (strcasecmp(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr, ev->sender_fpr) != 0) { // 2. If not, is it a group key? bool is_own_key = false; status = own_key_is_listed(session, ev->sender_fpr, &is_own_key); if (status) goto the_end; if (!is_own_key) { status = PEP_ILLEGAL_VALUE; goto the_end; } } } */ ev->sender_fpr = NULL; } // update own identities if (ev->own_identities && ev->own_identities->ident) { free_identity_list(session->«yml:lcase(@name)»_state.own.identities); session->«yml:lcase(@name)»_state.own.identities = ev->own_identities; ev->own_identities = NULL; } status = «@name»_driver(session, fsm, event); the_end: //free_«@name»_event(ev); // FIXME: We don't own this pointer. Are we sure it gets freed externally? return status; } || } apply "fsm", 0, mode=gen; } template "fsm", mode=send2 { || case Sync_PR_«yml:lcase(@name)»: { switch (message_type) { || if "message[@type='broadcast']" || // these messages are being broadcasted `` for "message[@type='broadcast']" |>> case «../@name»_PR_«yml:mixedCase(@name)»: status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(../@name)»); if (status) goto the_end; if (!(channels && channels->ident)) { // status = PEP_«yml:ucase(../@name)»_NO_CHANNEL; // we don't check for having a channel, because if // this is initial setup before having an own // identity we're fine goto the_end; } break; || || // these go anycast; previously used address is sticky (unicast) `` for "message[@type='anycast' and @security!='ignore']" |>> case «../@name»_PR_«yml:mixedCase(@name)»: // if we have a comm_partner fixed send it there if (session->«yml:lcase(../@name)»_state.comm_partner.identity) { pEp_identity *channel = identity_dup(session->«yml:lcase(../@name)»_state.comm_partner.identity); if (!channel) { status = PEP_OUT_OF_MEMORY; goto the_end; } channels = new_identity_list(channel); if (!channels) { status = PEP_OUT_OF_MEMORY; goto the_end; } } // if we can reply just do else if (session->«yml:lcase(../@name)»_state.transport.from) { pEp_identity *channel = identity_dup(session->«yml:lcase(../@name)»_state.transport.from); if (!channel) { status = PEP_OUT_OF_MEMORY; goto the_end; } channels = new_identity_list(channel); if (!channels) { status = PEP_OUT_OF_MEMORY; goto the_end; } } // real anycast, send it to the first matching else { status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(../@name)»); if (status) goto the_end; if (!channels) goto the_end; if (channels->next) { free_identity_list(channels->next); channels->next = NULL; } } break; default: status = PEP_«yml:ucase(../@name)»_ILLEGAL_MESSAGE; goto the_end; } } break; || } template "fsm[message/@security='ignore' or message/@ratelimit>0]", mode=send { || case Sync_PR_«yml:lcase(@name)»: { || if "message[@security='ignore']" { || // ignore switch (message_type) { || for "message[@security='ignore']" { |>> case «../@name»_PR_«yml:mixedCase(@name)»: |>>> return PEP_STATUS_OK; } || default: break; } || } if "message[@ratelimit>0]" { || // test if a message with a rate limit was just sent; in case drop switch (message_type) { || for "message[@ratelimit>0]" || case «../@name»_PR_«yml:mixedCase(@name)»: if (now < session->«yml:lcase(../../@name)»_state.own.last_«../@name»_«@name» + «@ratelimit») return PEP_STATUS_OK; break; || if "message[@ratelimit>0]" || default: break; } break; || || default: break; } || } } template "fsm", mode=timeout || /** * * * @internal * * @brief Determine if «@name» state machine has timed out * (by hanging in the same state too long, * exceeding «yml:ucase(@name)»_THRESHOLD) * * @param[in] state current state * * @retval true if wait has exceeded «yml:ucase(@name)»_THRESHOLD * false otherwise */ static bool _«@name»_timeout(int state) { static int last_state = None; static time_t switch_time = 0; if (state > Init) { if (state == last_state) { if (time(NULL) - switch_time > «yml:ucase(@name)»_THRESHOLD) { last_state = None; switch_time = 0; return true; } } else { last_state = state; switch_time = time(NULL); } } else { last_state = None; switch_time = 0; } return false; } || template "fsm", mode=reset_state_machine || case «../@name»_PR_«yml:lcase(@name)»: { int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state; switch (state) { `` for "state[@name!='InitState' and @timeout != 'off']" |>>> case «@name»: if (_«@name»_timeout(state)) { session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = Init; event = Init; `` if "@threshold > 0" |>>>>> «@name»TimeoutHandler(session); } break; default: _«@name»_timeout(None); } break; } || template "fsm", mode=signal_message { || case «../@name»_PR_«yml:lcase(@name)»: switch (msg->choice.«yml:lcase(@name)».present) { || for "message[@security='unencrypted']" { if "position()=1" |>> // these messages require a detached signature || case «../@name»_PR_«yml:mixedCase(@name)»: if (!sender_fpr) { status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE; goto the_end; } event = «@name»; break; || } for "message[@security='untrusted']" { if "position()=1" |>> // these messages must arrive encrypted || case «../@name»_PR_«yml:mixedCase(@name)»: if (rating < PEP_rating_reliable) { status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE; goto the_end; } event = «@name»; break; || } for "message[@security!='unencrypted' and @security!='untrusted' and @security!='ignore']" { if "position()=1" |>> // these messages must come through a trusted channel || case «../@name»_PR_«yml:mixedCase(@name)»: if (rating < PEP_rating_trusted) { status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE; goto the_end; } status = own_key_is_listed(session, sender_fpr, &is_own_key); if (status) goto the_end; if (!is_own_key) { status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE; goto the_end; } event = «@name»; break; || } for "message[@security='ignore']" || case «../@name»_PR_«yml:mixedCase(@name)»: free_«../../@name»_message(msg); return PEP_STATUS_OK; || || default: status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE; goto the_end; } break; || } template "fsm", mode=event { || case «../@name»_PR_«yml:lcase(@name)»: { switch (msg->choice.«yml:lcase(@name)».present) { || for "message" || case «../@name»_PR_«yml:mixedCase(@name)»: ev->event = «@name»; break; || || default: // unknown message type free(ev); return NULL; } break; } || } template "fsm", mode=driver || case «../@name»_PR_«yml:lcase(@name)»: { int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state; next_state = fsm_«@name»(session, state, event); if (next_state > None) { session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = next_state; event = Init; } else if (next_state < None) { return PEP_STATEMACHINE_ERROR - next_state; } break; } || template "fsm", mode=gen { if "count(state)>0" document "generated/{@name}_fsm.h", "text" { || /** * @file «@name»_fsm.h * @brief Finite state machine states, definitions, and structs for the «@name» protocol. * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #ifndef «yml:ucase(@name)»_FSM_H #define «yml:ucase(@name)»_FSM_H #include "«../@name»_impl.h" #ifdef __cplusplus extern "C" { #endif /////////////////////////////////////////////////////////////////////////////////////////// // state machine for «@name» /////////////////////////////////////////////////////////////////////////////////////////// // states typedef enum _«@name»_state { «@name»_state_None = None, «@name»_state_Init = Init, || for "func:distinctName(state[not(@name='InitState')])" |> «@name»`if "position()!=last()" > , ` || } «@name»_state; // events typedef enum _«@name»_event { «@name»_event_Timeout = None, «@name»_event_Init = Init, || for "message" { const "name", "@name"; |> «$name» = «/protocol/fsm/message[@name=$name]/@id», } |> «@name»_event_Extra = Extra, for "external" { if "@id < 128" error > external «@name» must have ID >= 128 but it's «@id» |> «@name» = «@id», } for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" { if "@name!='Init'" |> «@name»`if "position()!=last()" > , ` } || } «@name»_event; // state machine #ifndef NDEBUG /** * * * @brief Convert «@name» state to string * * @param[in] state state to convert to string * * @retval string representation of state name * NULL if invalid state */ const char *«@name»_state_name(int state); /** * * * @brief Convert «@name» event to string * * @param[in] event event to convert to string * * @retval string representation of event name * NULL if invalid event */ const char *«@name»_event_name(int event); #endif /** * * * @brief Given the current state of a «@name» state machine, determine if * a received «@name» event will cause a state transition * and, if so, return the next state. * * @param[in] session session state machine is associated with * @param[in] state current state * @param[in] event incoming event to evaluate * * @retval next_state if event causes a state transition * @retval None if state machine should remain in the same state * */ «@name»_state fsm_«@name»( PEP_SESSION session, «@name»_state state, «@name»_event event ); #ifdef __cplusplus } #endif #endif || } if "count(state)>0" document "generated/{@name}_fsm.c", "text" { || /** * @file «@name»_fsm.c * @brief Finite state machine implementation for the «@name» protocol. * @generated from ../sync/gen_statemachine.ysl2 * * @license GNU General Public License 3.0 - see LICENSE.txt */ #include "«@name»_fsm.h" #include #ifdef NDEBUG static #endif const char *«@name»_state_name(int state) { switch (state) { case End: return "End"; case None: return "None"; case Init: return "InitState"; || for "func:distinctName(state[not(@name='InitState')])" { |>> case «@name»: |>>> return "«@name»"; } || default: assert(0); return "unknown state"; } } #ifdef NDEBUG static #endif const char *«@name»_event_name(int event) { switch (event) { case None: return "Timeout"; case Init: return "Init"; || for "func:distinctName(state/event[not(@name='Init')]|message)" { |>> case «@name»: |>>> return "«@name»"; } || default: assert(0); return "unknown event"; } } /** * * * @internal * * @brief Convert an integer to a string representation * * @param[in] n integer to convert * @param[in] hex true if it should be in hex representation, * false for decimal * * @retval string representation of input * NULL if out of memory or input invalid */ static char *_str(int n, bool hex) { char *buf = calloc(1, 24); assert(buf); if (!buf) return NULL; if (hex) snprintf(buf, 24, "%.4x", n); else snprintf(buf, 24, "%d", n); return buf; } #define «@name»_ERR_LOG(t, d) log_event(session, (t), "«@name»", (d), "error") /** * * * @internal * * @brief Write a «@name» error to log with integer input as description * * @param[in] session relevant session * @param[in] t C string event name * @param[in] n integer to log (e.g. error status) * @param[in] hex true if integer should be logged in hex format, * false if decimal * * @retval PEP_OUT_OF_MEMORY if out of memory * @retval PEP_ILLEGAL_VALUE if input error during logging * @retval PEP_STATUS_OK otherwise */ __attribute__((__unused__)) static PEP_STATUS _«@name»_ERR_LOG_int(PEP_SESSION session, char *t, int n, bool hex) { char *_buf = _str(n, hex); if (!_buf) return PEP_OUT_OF_MEMORY; PEP_STATUS status = «@name»_ERR_LOG(t, _buf); free(_buf); return status; } #define «@name»_ERR_LOG_INT(t, n) _«@name»_ERR_LOG_int(session, (t), (n), false) #define «@name»_ERR_LOG_HEX(t, n) _«@name»_ERR_LOG_int(session, (t), (n), true) #define «@name»_SERVICE_LOG(t, d) SERVICE_LOG(session, (t), "«@name»", (d)) «@name»_state fsm_«@name»( PEP_SESSION session, «@name»_state state, «@name»_event event ) { assert(session); if (!session) return invalid_state; if ((int) state == None) state = «@name»_state_Init; switch (state) { `` apply "state", 2, mode=fsm default: «@name»_ERR_LOG("invalid state", «@name»_state_name(state)); assert(0); return invalid_state; } return None; } || } } template "state", mode=fsm { choose { when "@name='InitState'" | case «../@name»_state_Init: otherwise | case «@name»: } || «../@name»_SERVICE_LOG("in state", "«@name»"); switch (event) { case None: // received Timeout event, ignoring break; || if "not(event[@name='Init'])" || case Init: «../@name»_SERVICE_LOG("received Init but nothing to do", "Init"); break; || || `` apply "event", 2, mode=fsm default: // ignore events not handled here «../@name»_SERVICE_LOG("ignoring event", «../@name»_event_name(event)); return invalid_event; } break; || } template "event", mode=fsm { | case «@name»: { if "condition|action|send" |> PEP_STATUS status; if "condition" |> bool result = false; if "condition|action|send" | || «../../@name»_SERVICE_LOG("received event", "«@name»"); `` apply "transition|action|condition|else|send|debug"; || if "name(*[last()])!='transition'" { | |> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»"); |> break; } || } || } template "transition" { const "fsm", "ancestor::fsm"; || «$fsm/@name»_SERVICE_LOG("transition to state", "«@target»"); return «@target»; || } template "send" { const "fsm", "ancestor::fsm"; const "protocol", "ancestor::protocol"; || «$fsm/@name»_SERVICE_LOG("send message", "«@name»"); status = send_«$protocol/@name»_message(session, «$fsm/@id», «$fsm/@name»_PR_«yml:mixedCase(@name)»); if (status == PEP_OUT_OF_MEMORY) return out_of_memory; if (status) { «$fsm/@name»_ERR_LOG_HEX("sending «@name» failed", status); return cannot_send; } || } template "debug" | KeySync_SERVICE_LOG("«.»", "«ancestor::protocol/@name»"); template "action" { const "fsm", "ancestor::fsm"; || «$fsm/@name»_SERVICE_LOG("do action", "«@name»"); status = «@name»(session); if (status == PEP_OUT_OF_MEMORY) return out_of_memory; if (status) { «$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status); assert(0); return invalid_action; } || } template "condition" { const "fsm", "ancestor::fsm"; || status = «@name»(session, &result); if (status == PEP_OUT_OF_MEMORY) return out_of_memory; if (status) { «$fsm/@name»_ERR_LOG_HEX("computing condition «@name» failed", status); assert(0); return invalid_condition; } if (result) { «$fsm/@name»_SERVICE_LOG("condition applies", "«@name»"); || apply "transition|action|condition|else|send|debug"; | } } template "else" { if "not(name(preceding-sibling::*[1]) = 'condition')" error "else without if"; | else { |> «ancestor::fsm/@name»_SERVICE_LOG("condition does not apply", "«preceding-sibling::*[last()]/@name»"); apply "transition|action|condition|else|send|debug"; | } } }