diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5508fa96..7d0a5a50 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,14 +4,33 @@ include: stages: - deps - build + - build-docker - packages # Debian -debian10:build: +debian10:deps: extends: .make_in_docker + stage: deps + variables: + MAKE_TARGET: "deps" + CI_DISTRO_TARGET: "debian10" + DEBIAN_FRONTEND: "noninteractive" + rules: + - changes: + - DEPENDENCIES + - scripts/debian10/deps.pEpEngine.debian10.Dockerfile + - scripts/common/build_pEpEngine_deps.sh + +debian10:build: + tags: + - linux stage: build + image: ${DOCKER_REGISTRY_HOST}/pep-debian10-engine-deps:latest + script: + - cd scripts/${CI_DISTRO_TARGET} + - make ${MAKE_TARGET} variables: MAKE_TARGET: "build" CI_DISTRO_TARGET: "debian10" @@ -20,8 +39,13 @@ debian10:build: - if: '$CI_COMMIT_TAG !~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' debian10:tagged-build: - extends: .make_in_docker + tags: + - linux stage: build + image: ${DOCKER_REGISTRY_HOST}/pep-debian10-engine-deps:latest + script: + - cd scripts/${CI_DISTRO_TARGET} + - make ${MAKE_TARGET} variables: MAKE_TARGET: "build" CI_DISTRO_TARGET: "debian10" @@ -30,6 +54,29 @@ debian10:tagged-build: rules: - if: '$CI_COMMIT_TAG =~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' +debian10:build-docker: + extends: .make_in_docker + stage: build-docker + needs: ["debian10:build"] + variables: + MAKE_TARGET: "build-docker" + CI_DISTRO_TARGET: "debian10" + DEBIAN_FRONTEND: "noninteractive" + rules: + - if: '$CI_COMMIT_TAG !~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' + +debian10:tagged-build-docker: + extends: .make_in_docker + stage: build-docker + needs: ["debian10:tagged-build"] + variables: + MAKE_TARGET: "build-docker" + CI_DISTRO_TARGET: "debian10" + DEBIAN_FRONTEND: "noninteractive" + TAGGED_BUILD: "true" + rules: + - if: '$CI_COMMIT_TAG =~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' + # CentOS centos8:deps: @@ -41,11 +88,18 @@ centos8:deps: rules: - changes: - DEPENDENCIES + - scripts/centos8/deps.pEpEngine.centos8.Dockerfile + - scripts/common/build_pEpEngine_deps.sh centos8:build: - extends: .make_in_docker + tags: + - linux stage: build + image: ${DOCKER_REGISTRY_HOST}/pep-centos8-engine-deps:latest + script: + - cd scripts/${CI_DISTRO_TARGET} + - make ${MAKE_TARGET} variables: MAKE_TARGET: "build" CI_DISTRO_TARGET: "centos8" @@ -53,8 +107,13 @@ centos8:build: - if: '$CI_COMMIT_TAG !~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' centos8:tagged-build: - extends: .make_in_docker + tags: + - linux stage: build + image: ${DOCKER_REGISTRY_HOST}/pep-centos8-engine-deps:latest + script: + - cd scripts/${CI_DISTRO_TARGET} + - make ${MAKE_TARGET} variables: MAKE_TARGET: "build" CI_DISTRO_TARGET: "centos8" @@ -62,10 +121,31 @@ centos8:tagged-build: rules: - if: '$CI_COMMIT_TAG =~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' +centos8:build-docker: + extends: .make_in_docker + stage: build-docker + needs: ["centos8:build"] + variables: + MAKE_TARGET: "build-docker" + CI_DISTRO_TARGET: "centos8" + rules: + - if: '$CI_COMMIT_TAG !~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' + +centos8:tagged-build-docker: + extends: .make_in_docker + stage: build-docker + needs: ["centos8:tagged-build"] + variables: + MAKE_TARGET: "build-docker" + CI_DISTRO_TARGET: "centos8" + TAGGED_BUILD: "true" + rules: + - if: '$CI_COMMIT_TAG =~ /^Release_[0-9]+\.[0-9]+\.[0-9]+$/' + centos8:rpm: extends: .make_in_docker stage: packages - needs: ["centos8:build"] + needs: ["centos8:build-docker"] variables: MAKE_TARGET: "rpm" CI_DISTRO_TARGET: "centos8" @@ -76,7 +156,7 @@ centos8:rpm: centos8:rpm:tagged-build: extends: .upload_pkg stage: packages - needs: ["centos8:tagged-build"] + needs: ["centos8:tagged-build-docker"] variables: MAKE_TARGET: "rpm" CI_DISTRO_TARGET: "centos8" diff --git a/asn.1/Makefile b/asn.1/Makefile index 87de07b7..792c31b8 100644 --- a/asn.1/Makefile +++ b/asn.1/Makefile @@ -19,7 +19,7 @@ STORAGE_FILES = $(addsuffix .asn1, $(STORAGE)) .PHONY: all clean install uninstall -all: Sync.c Distribution.c Storage.c +all: Sync.c Distribution.c Storage.c ASN1Message.c $(MAKE) libasn1.a libasn1.a: $(ALL_OBJECTS) @@ -43,6 +43,11 @@ Storage.c: $(STORAGE_FILES) pEp.asn1 rm -f converter-sample.c touch Storage.c +ASN1Message.c: message.asn1 pEp.asn1 + $(ASN1C) -gen-PER $(ASN1C_OPTIONS) $+ + rm -f converter-sample.c + touch ASN1Message.c + clean: rm -f *.a *.o *.c *.h *.sample \ $(SYNC_FILES) $(DISTRIBUTION_FILES) $(STORAGE_FILES) diff --git a/asn.1/message.asn1 b/asn.1/message.asn1 new file mode 100644 index 00000000..2334d2ff --- /dev/null +++ b/asn.1/message.asn1 @@ -0,0 +1,94 @@ +-- This file is under BSD License 2.0 +-- +-- Sync protocol for p≡p +-- Copyright (c) 2021 p≡p foundation +-- +-- Written by Hartmut Goebel + + +PEP-MESSAGE { + iso(1) org(3) dod(6) internet(1) private(4) + enterprise(1) pEp(47878) message(1) + } +DEFINITIONS + AUTOMATIC TAGS EXTENSIBILITY IMPLIED ::= +BEGIN + +EXPORTS ASN1Message; + +IMPORTS + PString, PStringList, Identity, IdentityList, PStringPairList, Hash + FROM PEP { iso(1) org(3) dod(6) internet(1) private(4) + enterprise(1) pEp(47878) basic(0) }; + + +MessageDirection ::= ENUMERATED { + incoming (0), + outgoing (1) +} + +ContentDisposition ::= ENUMERATED { + attachment (0), + inline (1), + other (2) +} + +PBlob ::= SEQUENCE { + value OCTET STRING (SIZE(0..102400000)), -- up to 100 MB + mime-type PString OPTIONAL, + filename PString OPTIONAL, + disposition ContentDisposition +} +PBlobList ::= SEQUENCE OF PBlob + + +-- For the purposes of this profile, GeneralizedTime values MUST be +-- expressed in Coordinated Universal Time (UTC) and MUST include seconds +-- (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds +-- is zero. GeneralizedTime values MUST NOT include fractional seconds. + + +ASN1Message ::= SEQUENCE { + direction MessageDirection OPTIONAL, -- used only in "inner" messages + id PString OPTIONAL, -- UTF-8 string of message ID + sent GeneralizedTime OPTIONAL, -- when the message is sent + recv GeneralizedTime OPTIONAL, -- when the message is received + from Identity, -- whom the message is from + -- At least one of to, cc must not be an empty list + -- FIXME: Can this be constrained? + to IdentityList OPTIONAL, -- whom the message is to + cc IdentityList OPTIONAL, -- whom a CC is being sent + bcc IdentityList OPTIONAL, -- whom a BCC is being sent + recv-by Identity OPTIONAL, -- via which identity the message + -- is received + reply-to IdentityList OPTIONAL, -- where a reply should go to + in-reply-to PStringList OPTIONAL, -- list of UTF-8 strings with + -- MessageIDs of refering messages + -- internal: refering_msg_ref + references PStringList OPTIONAL, -- list of UTF-8 strings with + -- references + -- internal: refered_by _message-ref-list + keywords PStringList OPTIONAL, -- list of UTF-8 strings with keywords + comments PString OPTIONAL, -- UTF-8 string with comments + opt-fields PStringPairList OPTIONAL, -- optional fields + sender-fpr Hash OPTIONAL, -- fingerprint of sending signer + + -- At least one of shortmsg, longmsg, longmsg-formatted must be present + -- FIXME: Can this be constrained? + shortmsg PString OPTIONAL, -- UTF-8 string of short message + + -- longmsg must only be used if implementation can handle dynamic allocation + longmsg UTF8String OPTIONAL, -- UTF-8 string of long message + -- (plain) + + -- longmsg-formatted must only be used if implementation can handle + -- dynamic allocation + longmsg-formatted UTF8String OPTIONAL, -- UTF-8 string of long message + -- (formatted) + + attachments PBlobList OPTIONAL -- blobs with attachments + -- internal: rawmsg +} + +END + diff --git a/asn.1/pEp.asn1 b/asn.1/pEp.asn1 index 540bfbd9..21606228 100644 --- a/asn.1/pEp.asn1 +++ b/asn.1/pEp.asn1 @@ -12,12 +12,13 @@ DEFINITIONS AUTOMATIC TAGS EXTENSIBILITY IMPLIED ::= BEGIN -EXPORTS Identity, IdentityList, TID, Hash, Version, Rating; +EXPORTS Identity, IdentityList, TID, Hash, Version, Rating, PString, PStringList, PStringPair, PStringPairList; ISO639-1 ::= PrintableString(FROM ("a".."z")) (SIZE(2)) -Hex ::= PrintableString(FROM ("A".."F") | FROM ("0".."9")) +Hex ::= PrintableString(FROM ("A".."F" | "0".."9")) Hash ::= Hex(SIZE(16..128)) -- 32bit Key ID to SHA512 in hex -PString ::= UTF8String (SIZE(1..1024)) +PString ::= UTF8String (SIZE(0..1024)) +PStringList ::= SEQUENCE OF PString TID ::= OCTET STRING (SIZE(16)) -- UUID version 4 variant 1 Identity ::= SEQUENCE { @@ -62,5 +63,12 @@ Rating ::= ENUMERATED { under-attack (-3) } +PStringPair ::= SEQUENCE { + key PString, + value PString +} + +PStringPairList ::= SEQUENCE OF PStringPair + END diff --git a/build-mac/pEpEngine.xcodeproj/project.pbxproj b/build-mac/pEpEngine.xcodeproj/project.pbxproj index c764aafd..ae9d461e 100644 --- a/build-mac/pEpEngine.xcodeproj/project.pbxproj +++ b/build-mac/pEpEngine.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 1543DA4C2577F8BE0041EFB5 /* Sync_event.h in Headers */ = {isa = PBXBuildFile; fileRef = 43188A8823C4B2DD008EF79C /* Sync_event.h */; }; 1543DAA9257801880041EFB5 /* baseprotocol.c in Sources */ = {isa = PBXBuildFile; fileRef = 646C414C1D510D8800C63EFF /* baseprotocol.c */; }; 1543DAAD257801A90041EFB5 /* resource_id.c in Sources */ = {isa = PBXBuildFile; fileRef = 43F6921C1F164A47009418F5 /* resource_id.c */; }; - 1549181222B92EA20091B6D6 /* libiconv.2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1549180D22B92EA20091B6D6 /* libiconv.2.tbd */; }; 154918AB22B940200091B6D6 /* fsm_common.h in Headers */ = {isa = PBXBuildFile; fileRef = 1549189222B9401D0091B6D6 /* fsm_common.h */; platformFilter = ios; }; 154918AC22B940200091B6D6 /* status_to_string.h in Headers */ = {isa = PBXBuildFile; fileRef = 1549189322B9401E0091B6D6 /* status_to_string.h */; platformFilter = ios; }; 154918B222B940200091B6D6 /* aux_mime_msg.h in Headers */ = {isa = PBXBuildFile; fileRef = 1549189922B9401E0091B6D6 /* aux_mime_msg.h */; platformFilter = ios; }; @@ -310,7 +309,6 @@ 154917FA22B926700091B6D6 /* libsequoia_openpgp_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsequoia_openpgp_ffi.a; path = ../../sequoia4ios/build/lib/libsequoia_openpgp_ffi.a; sourceTree = ""; }; 154917FB22B926700091B6D6 /* libhogweed.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libhogweed.a; path = ../../sequoia4ios/build/lib/libhogweed.a; sourceTree = ""; }; 154917FC22B926700091B6D6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = ../../sequoia4ios/build/lib/libgmp.a; sourceTree = ""; }; - 1549180D22B92EA20091B6D6 /* libiconv.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.2.tbd; path = usr/lib/libiconv.2.tbd; sourceTree = SDKROOT; }; 1549189222B9401D0091B6D6 /* fsm_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fsm_common.h; path = ../src/fsm_common.h; sourceTree = ""; }; 1549189322B9401E0091B6D6 /* status_to_string.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = status_to_string.h; path = ../src/status_to_string.h; sourceTree = ""; }; 1549189922B9401E0091B6D6 /* aux_mime_msg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = aux_mime_msg.h; path = ../src/aux_mime_msg.h; sourceTree = ""; }; @@ -426,7 +424,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1549181222B92EA20091B6D6 /* libiconv.2.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -546,7 +543,6 @@ 1585EF5A26A08B2000B822E1 /* CommonPEPDependencies.xcodeproj */, 15A3261C2611F6B5009D07EB /* PEPSQLITE3.xcodeproj */, 15A3203B260E030B009D07EB /* pEpASN1.xcodeproj */, - 1549180D22B92EA20091B6D6 /* libiconv.2.tbd */, 64951A1B1BE0FCD800B10E71 /* system.db */, 64DA24121B832EBA000BEE80 /* libetpan.xcodeproj */, 43188A5523C4A7F8008EF79C /* sync-generated */, diff --git a/codegen/Makefile b/codegen/Makefile index 5c679018..3afa49e8 100644 --- a/codegen/Makefile +++ b/codegen/Makefile @@ -44,5 +44,5 @@ svg: $(patsubst %.dot,%.svg,$(wildcard *.dot)) $(YML2_PROC) -y gen_dot.ysl2 $< -o $@ %.svg: %.dot - dot -Tsvg -o $@ $< + dot -Tsvg $< -o $@ > /dev/null diff --git a/codegen/gen_codec.ysl2 b/codegen/gen_codec.ysl2 index 8aae0382..131c0fe9 100644 --- a/codegen/gen_codec.ysl2 +++ b/codegen/gen_codec.ysl2 @@ -152,8 +152,13 @@ tstylesheet { *msg = NULL; «@name»_t *_msg = NULL; - uper_decode_complete(NULL, &asn_DEF_«@name», (void **) &_msg, data, size); - if (!_msg) + asn_dec_rval_t rval = uper_decode_complete(NULL, &asn_DEF_«@name», (void **) &_msg, data, size); + + // N.B: If you plan on having messages were the full message isn't consumed by decoding here, + // then please look into uper_decode_complete; we still may get a message, even if to contains + // nothing. RC_FAIL is an obvious case, but we also need to fail if RC_WMORE is the code, especially + // if rval.consumed == 0. Volker, please look into this and decide what you want. + if (!_msg || rval.code != RC_OK) return PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; *msg = _msg; diff --git a/codegen/gen_dot.ysl2 b/codegen/gen_dot.ysl2 index 03b785de..c7f2a6da 100644 --- a/codegen/gen_dot.ysl2 +++ b/codegen/gen_dot.ysl2 @@ -4,7 +4,7 @@ include yslt.yml2 tstylesheet { - template "protocol/fsm[count(state)>0]" document "{@name}.dot", "text" + template "/protocol/fsm[count(state)>0]" || digraph finite_state_machine { rankdir=LR; diff --git a/codegen/gen_statemachine.ysl2 b/codegen/gen_statemachine.ysl2 index bc206dff..07019b6f 100644 --- a/codegen/gen_statemachine.ysl2 +++ b/codegen/gen_statemachine.ysl2 @@ -1705,7 +1705,7 @@ tstylesheet { switch (event) { case None: - «../@name»_SERVICE_LOG("received Timeout event", "ignoring"); + // received Timeout event, ignoring break; || diff --git a/db/Makefile b/db/Makefile index cd8362b9..af5fdbd0 100644 --- a/db/Makefile +++ b/db/Makefile @@ -18,8 +18,11 @@ system.db: create_system_db.sql $(DICOS) .PHONY: install install: system.db - mkdir -p $(DESTDIR)$(PER_MACHINE_DIRECTORY) - cp system.db $(DESTDIR)$(PER_MACHINE_DIRECTORY) + # Notice that the expansion of these make variables may still contain + # shell variable occurrences, to be expanded in their turn. This + # serves to leave more freedom to the deployment engineer. + mkdir -p "$(DESTDIR)$(PER_MACHINE_DIRECTORY)" + cp system.db "$(DESTDIR)$(PER_MACHINE_DIRECTORY)" .PHONY: clean clean: diff --git a/doc/build-debian.md b/doc/build-debian.md index 56edca16..9bc295e4 100644 --- a/doc/build-debian.md +++ b/doc/build-debian.md @@ -1,43 +1,56 @@ - -# Build instructions for Debian 9 +# Build instructions for Debian 9 and 10 + +We assume the user keeps sources under `~/pep-src`. There is no single +installation prefix: each package is built, or when supported installed, in +either its source directory or in a subdirectory of its source directory. +Rationale: we do not pollute the user system, uninstalling is trivial. + +This is Unix: we assume no spaces in user names. + +~~~ +mkdir -p ~/pep-src +~~~ # Installing packaged dependencies ~~~ # general -apt install -y mercurial -# YML2 -apt install -y python-lxml +apt install -y git # libetpan apt install -y git build-essential automake libtool # asn1c apt install -y git build-essential automake libtool autoconf +# sequoia +apt install git rustc cargo clang libclang-dev make pkg-config nettle-dev libssl-dev capnproto libsqlite3-dev # engine apt install -y uuid-dev libgpgme-dev libsqlite3-dev sqlite3 +# optional: developer documentation +apt install -y doxygen pandoc ~~~ # Installing unpackaged dependencies ## YML2 ~~~ -mkdir -p ~/code/yml2 -git clone https://gitea.pep.foundation/fdik/yml2.git ~/code/yml2 +mkdir -p ~/pep-src/yml2 +git clone https://gitea.pep.foundation/fdik/yml2.git ~/pep-src/yml2 ~~~ ## libetpan pEp Engine requires libetpan with a set of patches that have not been upstreamed yet. ~~~ -mkdir -p ~/code/libetpan +mkdir -p ~/pep-src/libetpan -git clone https://gitea.pep.foundation/pEp.foundation/libetpan.git ~/code/libetpan -cd ~/code/libetpan -mkdir ~/code/libetpan/build -./autogen.sh --prefix="$HOME/code/libetpan/build" +git clone https://gitea.pep.foundation/pEp.foundation/libetpan.git ~/pep-src/libetpan +cd ~/pep-src/libetpan +mkdir ~/pep-src/libetpan/build +./autogen.sh --prefix="$HOME/pep-src/libetpan/build" make make install ~~~ @@ -45,45 +58,70 @@ make install ## asn1c ~~~ -mkdir -p ~/code/asn1c -git clone git://github.com/vlm/asn1c.git ~/code/asn1c -cd ~/code/asn1c +mkdir -p ~/pep-src/asn1c +git clone git://github.com/vlm/asn1c.git ~/pep-src/asn1c +cd ~/pep-src/asn1c git checkout tags/v0.9.28 -b pep-engine autoreconf -iv -mkdir ~/code/asn1c/build -./configure --prefix="$HOME/code/asn1c/build" +mkdir ~/pep-src/asn1c/build +./configure --prefix="$HOME/pep-src/asn1c/build" make make install ~~~ +## sequoia + +~~~ +git clone https://gitlab.com/sequoia-pgp/sequoia +cd ~/pep-src/sequoia +git checkout openpgp/v1.3.0 +# Make an optimised sequoia build. +cargo build --all --release -j16 +~~~ + +This alternative for the last line above is faster, but generates compiled libraries +in `~/pep-src/sequoia/target/debug` instead of `~/pep-src/sequoia/target/release`: +several definitions below need to be adapted. +~~~ +# Alternative: make a debugging sequoia build. +cargo build --all -j16 +~~~ + # pEp Engine ~~~ -mkdir -p ~/code/pep-engine -hg clone https://pep.foundation/dev/repos/pEpEngine/ ~/code/pep-engine -cd ~/code/pep-engine -mkdir ~/code/pep-engine/build +mkdir -p ~/pep-src/pep-engine +git clone https://gitea.pep.foundation/pEp.foundation/pEpEngine ~/pep-src/pep-engine +cd ~/pep-src/pep-engine +mkdir build ~~~ -Edit the build configuration to your needs in `Makefile.conf`, or create a `local.conf` that sets any of the make variables documented in `Makefile.conf`. All the default values for the build configuration variables on each platform are documented in `Makefile.conf`. +Edit the build configuration to your needs in `Makefile.conf`, or create a `local.conf` in your source directory (the same containing `Makefile.conf`) that sets any of the make variables documented in `Makefile.conf`. All the default values for the build configuration variables on each platform are documented in `Makefile.conf`. If a dependency is not found in your system's default include or library paths, you will have to specify the according paths in a make variable. Typically, this has to be done at least for YML2, libetpan and asn1c. For a more detailed explanation of the mechanics of these build configuration files, and overriding defaults, see the comments in `Makefile.conf`. -Below is a sample `./local.conf` file, for orientation. +The following `./local.conf` example should work in the configuration described here. ~~~ -PREFIX=$(HOME)/code/pep-engine/build +PREFIX=$(HOME)/pep-src/pep-engine/build PER_MACHINE_DIRECTORY=$(PREFIX)/share/pEp -YML2_PATH=$(HOME)/code/yml2 +YML2_PATH=$(HOME)/pep-src/yml2 + +ETPAN_LIB=-L$(HOME)/pep-src/libetpan/build/lib +ETPAN_INC=-I$(HOME)/pep-src/libetpan/build/include + +ASN1C=$(HOME)/pep-src/asn1c/build/bin/asn1c +ASN1C_INC=-I$(HOME)/pep-src/asn1c/build/share/asn1c -ETPAN_LIB=-L$(HOME)/code/libetpan/build/lib -ETPAN_INC=-I$(HOME)/code/libetpan/build/include +SEQUOIA_INC=-I$(HOME)/pep-src/sequoia/openpgp-ffi/include +SEQUOIA_LDFLAGS=-L$(HOME)/pep-src/sequoia/target/release -ASN1C=$(HOME)/code/asn1c/build/bin/asn1c -ASN1C_INC=-I$(HOME)/code/asn1c/build/share/asn1c +GTEST_SRC_DIR=$(HOME)/pep-src/googletest/googletest +GTEST_INC_DIR=$(HOME)/pep-src/googletest/googletest/include +GTEST_PL=$(HOME)/pep-src/gtest-parallel/gtest_parallel.py ~~~ The engine is built as follows: diff --git a/scripts/centos8/Makefile b/scripts/centos8/Makefile index ab60889d..c0e6e9b1 100644 --- a/scripts/centos8/Makefile +++ b/scripts/centos8/Makefile @@ -22,7 +22,7 @@ endif all: deps build deps: - -docker pull $(IMAGE_NAME)-deps:latest + -docker pull --quiet $(IMAGE_NAME)-deps:latest cd ../../ && docker build --build-arg CURRENT_DISTRO=$(CURRENT_DISTRO) \ --build-arg DOCKER_REGISTRY_HOST=${DOCKER_REGISTRY_HOST} \ --build-arg PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ @@ -33,11 +33,14 @@ deps: --tag=$(IMAGE_NAME)-deps:$(SEQUOIA_VERSION)-$(YML2_VERSION) \ --tag=$(IMAGE_NAME)-deps:latest \ -f scripts/${CURRENT_DISTRO}/deps.$(DOCKERFILE) . - docker push $(IMAGE_NAME)-deps:$(SEQUOIA_VERSION)-$(YML2_VERSION) - docker push $(IMAGE_NAME)-deps:latest + docker push --quiet $(IMAGE_NAME)-deps:$(SEQUOIA_VERSION)-$(YML2_VERSION) + docker push --quiet $(IMAGE_NAME)-deps:latest build: - -docker pull $(IMAGE_NAME):latest + cd ../../ && /bin/sh ./scripts/common/build_pEpEngine.sh + +build-docker: + -docker pull --quiet $(IMAGE_NAME):latest cd ../../ && docker build --build-arg CURRENT_DISTRO=$(CURRENT_DISTRO) \ --build-arg DOCKER_REGISTRY_HOST=${DOCKER_REGISTRY_HOST} \ --build-arg PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ @@ -47,12 +50,12 @@ build: --cache-from $(IMAGE_NAME):latest \ --tag=$(IMAGE_NAME):$(PEPENGINE_VERSION) \ --tag=$(IMAGE_NAME):latest \ - -f scripts/${CURRENT_DISTRO}/$(DOCKERFILE) . - docker push $(IMAGE_NAME):$(PEPENGINE_VERSION) - docker push $(IMAGE_NAME):latest + -f scripts/common/build.pEpEngine.Dockerfile . + docker push --quiet $(IMAGE_NAME):$(PEPENGINE_VERSION) + docker push --quiet $(IMAGE_NAME):latest rpm: - -docker pull $(PKG_BUILD_IMAGE)-engine:latest + -docker pull --quiet $(PKG_BUILD_IMAGE)-engine:latest @docker build --build-arg CURRENT_DISTRO=$(CURRENT_DISTRO) \ --build-arg PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ --build-arg DOCKER_REGISTRY_HOST=${DOCKER_REGISTRY_HOST} \ @@ -62,8 +65,8 @@ rpm: --tag=$(PKG_BUILD_IMAGE)-engine:$(PEPENGINE_VERSION) \ --tag=$(PKG_BUILD_IMAGE)-engine:latest \ packages/rpm - @docker push $(PKG_BUILD_IMAGE)-engine:$(PEPENGINE_VERSION) - @docker push $(PKG_BUILD_IMAGE)-engine:latest + @docker push --quiet $(PKG_BUILD_IMAGE)-engine:$(PEPENGINE_VERSION) + @docker push --quiet $(PKG_BUILD_IMAGE)-engine:latest @docker run -e PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ -e PEP_MACHINE_DIR=$(PEP_MACHINE_DIR) \ -e PKG_VERSION=$(PEPENGINE_VERSION) \ diff --git a/scripts/centos8/deps.pEpEngine.centos8.Dockerfile b/scripts/centos8/deps.pEpEngine.centos8.Dockerfile index 94ca52b6..e0f210ee 100644 --- a/scripts/centos8/deps.pEpEngine.centos8.Dockerfile +++ b/scripts/centos8/deps.pEpEngine.centos8.Dockerfile @@ -17,6 +17,9 @@ USER root RUN yum install -y python3 python3-lxml binutils && yum clean all +### Setup PEP_MACHINE_DIR +RUN mkdir -p ${PEP_MACHINE_DIR} + RUN chown -R pep-builder:pep-builder ${BUILDROOT}/pEpEngine WORKDIR ${BUILDROOT}/pEpEngine diff --git a/scripts/centos8/pEpEngine.centos8.Dockerfile b/scripts/common/build.pEpEngine.Dockerfile similarity index 90% rename from scripts/centos8/pEpEngine.centos8.Dockerfile rename to scripts/common/build.pEpEngine.Dockerfile index 635799ee..1d6ef779 100644 --- a/scripts/centos8/pEpEngine.centos8.Dockerfile +++ b/scripts/common/build.pEpEngine.Dockerfile @@ -12,7 +12,8 @@ ARG PEP_MACHINE_DIR ### Setup working directory USER root -RUN mkdir -p ${BUILDROOT}/pEpEngine +RUN rm -rf ${BUILDROOT}/pEpEngine && \ + mkdir -p ${BUILDROOT}/pEpEngine COPY . ${BUILDROOT}/pEpEngine RUN chown -R pep-builder:pep-builder ${BUILDROOT}/pEpEngine diff --git a/scripts/debian10/Makefile b/scripts/debian10/Makefile index da5399cf..24c78f18 100644 --- a/scripts/debian10/Makefile +++ b/scripts/debian10/Makefile @@ -6,6 +6,7 @@ SEQUOIA_VERSION=$(shell echo ${sequoia} | sed 's/\//-/') CURRENT_DISTRO=$(shell basename $(shell pwd)) IMAGE_NAME=${DOCKER_REGISTRY_HOST}/pep-$(CURRENT_DISTRO)-engine DOCKERFILE=pEpEngine.$(CURRENT_DISTRO).Dockerfile +PKG_INSTALL_PATH=/opt/pEp IS_TAGGED=${TAGGED_BUILD} ifeq ($(IS_TAGGED), true) # $CI_COMMIT_TAG is a predefined environment variable from Gitlab @@ -13,10 +14,28 @@ ifeq ($(IS_TAGGED), true) else PEPENGINE_VERSION=$(shell git rev-parse --short=8 HEAD) endif -all: build +all: deps build + +deps: + -docker pull --quiet $(IMAGE_NAME)-deps:latest + cd ../../ && docker build --build-arg CURRENT_DISTRO=$(CURRENT_DISTRO) \ + --build-arg DOCKER_REGISTRY_HOST=${DOCKER_REGISTRY_HOST} \ + --build-arg PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ + --build-arg SEQUOIA_VERSION=$(SEQUOIA_VERSION) \ + --build-arg YML2_VERSION=$(YML2_VERSION) \ + --build-arg PEP_MACHINE_DIR=$(PEP_MACHINE_DIR) \ + --cache-from $(IMAGE_NAME):latest \ + --tag=$(IMAGE_NAME)-deps:$(SEQUOIA_VERSION)-$(YML2_VERSION) \ + --tag=$(IMAGE_NAME)-deps:latest \ + -f scripts/${CURRENT_DISTRO}/deps.$(DOCKERFILE) . + docker push --quiet $(IMAGE_NAME)-deps:$(SEQUOIA_VERSION)-$(YML2_VERSION) + docker push --quiet $(IMAGE_NAME)-deps:latest build: - -docker pull $(IMAGE_NAME):latest + cd ../../ && /bin/sh ./scripts/common/build_pEpEngine.sh + +build-docker: + -docker pull --quiet $(IMAGE_NAME):latest cd ../../ && docker build --build-arg CURRENT_DISTRO=$(CURRENT_DISTRO) \ --build-arg DOCKER_REGISTRY_HOST=${DOCKER_REGISTRY_HOST} \ --build-arg PEPENGINE_VERSION=$(PEPENGINE_VERSION) \ @@ -26,6 +45,6 @@ build: --cache-from $(IMAGE_NAME):latest \ --tag=$(IMAGE_NAME):$(PEPENGINE_VERSION) \ --tag=$(IMAGE_NAME):latest \ - -f scripts/${CURRENT_DISTRO}/$(DOCKERFILE) . - docker push $(IMAGE_NAME):$(PEPENGINE_VERSION) - docker push $(IMAGE_NAME):latest + -f scripts/common/build.pEpEngine.Dockerfile . + docker push --quiet $(IMAGE_NAME):$(PEPENGINE_VERSION) + docker push --quiet $(IMAGE_NAME):latest diff --git a/scripts/debian10/pEpEngine.debian10.Dockerfile b/scripts/debian10/deps.pEpEngine.debian10.Dockerfile similarity index 63% rename from scripts/debian10/pEpEngine.debian10.Dockerfile rename to scripts/debian10/deps.pEpEngine.debian10.Dockerfile index 088ccd58..e2663535 100644 --- a/scripts/debian10/pEpEngine.debian10.Dockerfile +++ b/scripts/debian10/deps.pEpEngine.debian10.Dockerfile @@ -11,10 +11,16 @@ ARG PEP_MACHINE_DIR ### Setup working directory RUN mkdir ${BUILDROOT}/pEpEngine -COPY . ${BUILDROOT}/pEpEngine +COPY ./scripts/common/build_pEpEngine_deps.sh ${BUILDROOT}/pEpEngine USER root +RUN apt-get update && apt-get install -y bzip2 && \ + rm -rf /var/lib/apt/lists/* + +### Setup PEP_MACHINE_DIR +RUN mkdir -p ${PEP_MACHINE_DIR} + RUN chown -R pep-builder:pep-builder ${BUILDROOT}/pEpEngine WORKDIR ${BUILDROOT}/pEpEngine @@ -22,19 +28,7 @@ ARG YML2_VERSION ARG ENGINE_VERSION ARG CURRENT_DISTRO -RUN apt-get update && apt-get install -y bzip2 && \ - rm -rf /var/lib/apt/lists/* - ### Build pEpEngine dependencies USER pep-builder -RUN sh ./scripts/common/build_pEpEngine_deps.sh - -### Build pEpEngine -RUN sh ./scripts/common/build_pEpEngine.sh - -### Install Systemdb -USER root - -RUN sh ./scripts/common/install_pEpEngine_systemdb.sh && \ - rm -rf ${BUILDROOT}/* +RUN sh ./build_pEpEngine_deps.sh diff --git a/src/Makefile b/src/Makefile index 8cdb7c2e..0eb3766e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,11 +5,21 @@ include ../Makefile.conf -CFLAGS+= $(ETPAN_INC) $(PEP_MIME_INC) -I../asn.1 $(EXTRA_MACROS) -LDFLAGS+= $(ETPAN_LIB) $(PEP_MIME_LIB) -L../asn.1 +# Notice the single quotes below: since user definitions can contain dollar +# signs it is important to prevent their expansion: shell variable references +# here must be expanded at run time, and not at compile time. +ifdef PER_USER_DIRECTORY + EXTRA_MACROS+= -DPER_USER_DIRECTORY='"$(PER_USER_DIRECTORY)"' +endif + +ifdef PER_MACHINE_DIRECTORY + EXTRA_MACROS+= -DPER_MACHINE_DIRECTORY='"$(PER_MACHINE_DIRECTORY)"' +endif + ifneq ($(BUILD_ON),OS/390) LDFLAGS+= -shared endif + NO_SOURCE= ifndef PEP_MIME @@ -113,17 +123,20 @@ clean: rm -Rf $(TARGET).dSYM rm -f KeySync_fsm.* Sync_actions.c Sync_event.* Sync_func.* Sync_impl.* sync_codec.* distribution_codec.* storage_codec.* +HEADERS_TO_INSTALL = \ + pEpEngine.h keymanagement.h message_api.h dynamic_api.h stringlist.h \ + timestamp.h identity_list.h bloblist.h stringpair.h message.h mime.h group.h \ + cryptotech.h sync_api.h pEp_string.h openpgp_compat.h engine_sql.h \ + labeled_int_list.h key_reset.h base64.h sync_codec.h distribution_codec.h \ + storage_codec.h status_to_string.h keyreset_command.h platform.h platform_unix.h \ + transport.h growing_buf.h $(wildcard ../asn.1/*.h) + # CAVEAT: # install_headers is needed for building *STANDALONE* pEp MIME - it is NOT used for built-in functionality!!! install_headers: $(TARGET) mkdir -p $(DESTDIR)$(PREFIX)/include/pEp - cp pEpEngine.h keymanagement.h message_api.h dynamic_api.h stringlist.h \ - timestamp.h identity_list.h bloblist.h stringpair.h message.h mime.h group.h \ - cryptotech.h sync_api.h pEp_string.h openpgp_compat.h engine_sql.h \ - labeled_int_list.h key_reset.h base64.h sync_codec.h distribution_codec.h storage_codec.h \ - status_to_string.h keyreset_command.h platform.h platform_unix.h ../asn.1/*.h \ - $(DESTDIR)$(PREFIX)/include/pEp/ + cp $(HEADERS_TO_INSTALL) $(DESTDIR)$(PREFIX)/include/pEp/ # FIXME: Does anyone but Roker use install_headers? Otherwise, remove the dependency. install: $(TARGET) install_headers @@ -136,7 +149,8 @@ beinstall: install uninstall: rm -f $(DESTDIR)$(PREFIX)/lib/$(TARGET) - rm -rf $(DESTDIR)$(PREFIX)/include/pEp + rm $(addprefix $(DESTDIR)$(PREFIX)/include/pEp/,$(notdir $(HEADERS_TO_INSTALL))) + rmdir $(DESTDIR)$(PREFIX)/include/pEp 2> /dev/null || true tags: $(wildcard *.c) $(wildcard *.h) ctags --sort=yes *.c *.h diff --git a/src/engine_sql.c b/src/engine_sql.c index a983c5ea..344fb96d 100644 --- a/src/engine_sql.c +++ b/src/engine_sql.c @@ -713,7 +713,7 @@ static PEP_STATUS _create_core_tables(PEP_SESSION session) { " timestamp integer default (datetime('now')),\n" " primary key (address, user_id)\n" ");\n" - "create index if not exists identity_userid_addr on identity(address, user_id);\n" + "create index if not exists identity_userid on identity (user_id);\n" "create table if not exists trust (\n" " user_id text not null\n" " references person (id)\n" @@ -1548,6 +1548,27 @@ static PEP_STATUS _upgrade_DB_to_ver_18(PEP_SESSION session) { return _force_upgrade_own_latest_message_version(session); } +static PEP_STATUS _upgrade_DB_to_ver_19(PEP_SESSION session) { + int int_result = sqlite3_exec( + session->db, + /* This index was useless: it was an index on the (multi-column) + primary key, always implemented using an index which gets also + used in queries. */ + "drop index if exists identity_userid_addr;\n" + "\n" + "create index if not exists identity_userid on identity (user_id);\n", + NULL, + NULL, + NULL + ); + assert(int_result == SQLITE_OK); + + if (int_result != SQLITE_OK) + return PEP_UNKNOWN_DB_ERROR; + + return PEP_STATUS_OK; +} + // Honestly, the upgrades should be redone in a transaction IMHO. static PEP_STATUS _check_and_execute_upgrades(PEP_SESSION session, int version) { PEP_STATUS status = PEP_STATUS_OK; @@ -1620,6 +1641,10 @@ static PEP_STATUS _check_and_execute_upgrades(PEP_SESSION session, int version) if (status != PEP_STATUS_OK) return status; case 18: + status = _upgrade_DB_to_ver_19(session); + if (status != PEP_STATUS_OK) + return status; + case 19: break; default: return PEP_ILLEGAL_VALUE; diff --git a/src/engine_sql.h b/src/engine_sql.h index 0bcd6415..25d98ea4 100644 --- a/src/engine_sql.h +++ b/src/engine_sql.h @@ -3,7 +3,7 @@ #include "pEp_internal.h" // increment this when patching DDL -#define _DDL_USER_VERSION "18" +#define _DDL_USER_VERSION "19" PEP_STATUS init_databases(PEP_SESSION session); PEP_STATUS pEp_sql_init(PEP_SESSION session); diff --git a/src/keymanagement.c b/src/keymanagement.c index 315546a8..7e4f60f5 100644 --- a/src/keymanagement.c +++ b/src/keymanagement.c @@ -875,13 +875,6 @@ DYNAMIC_API PEP_STATUS update_identity( if (identity->comm_type == PEP_ct_unknown) identity->comm_type = PEP_ct_key_not_found; } - - // VB says, and I quote, "that is not implemented and no one is using it right now" - // about this bit. So, um, you're forewarned. - if (identity->comm_type != PEP_ct_compromised && - identity->comm_type < PEP_ct_strong_but_unconfirmed) - if (session->examine_identity) - session->examine_identity(identity, session->examine_management); goto pEp_free; @@ -1256,66 +1249,6 @@ DYNAMIC_API PEP_STATUS myself(PEP_SESSION session, pEp_identity * identity) return _myself(session, identity, true, true, false, false); } -DYNAMIC_API PEP_STATUS register_examine_function( - PEP_SESSION session, - examine_identity_t examine_identity, - void *management - ) -{ - assert(session); - if (!session) - return PEP_ILLEGAL_VALUE; - - session->examine_management = management; - session->examine_identity = examine_identity; - - return PEP_STATUS_OK; -} - -DYNAMIC_API PEP_STATUS do_keymanagement( - retrieve_next_identity_t retrieve_next_identity, - void *management - ) -{ - PEP_SESSION session; - pEp_identity *identity; - // FIXME_NOW: ensure_decrypt callback??? - PEP_STATUS status = init(&session, NULL, NULL, NULL); - assert(!status); - if (status) - return status; - - assert(session && retrieve_next_identity); - if (!(session && retrieve_next_identity)) - return PEP_ILLEGAL_VALUE; - - log_event(session, "keymanagement thread started", "pEp engine", NULL, NULL); - - while ((identity = retrieve_next_identity(management))) - { - assert(identity->address); - if(identity->address) - { - DEBUG_LOG("do_keymanagement", "retrieve_next_identity", identity->address); - - if (identity->me) { - status = myself(session, identity); - } else { - status = recv_key(session, identity->address); - } - - assert(status != PEP_OUT_OF_MEMORY); - if(status == PEP_OUT_OF_MEMORY) - return PEP_OUT_OF_MEMORY; - } - free_identity(identity); - } - - log_event(session, "keymanagement thread shutdown", "pEp engine", NULL, NULL); - release(session); - return PEP_STATUS_OK; -} - DYNAMIC_API PEP_STATUS key_mistrusted( PEP_SESSION session, pEp_identity *ident diff --git a/src/keymanagement.h b/src/keymanagement.h index 18a38b3b..ce097fc1 100644 --- a/src/keymanagement.h +++ b/src/keymanagement.h @@ -122,92 +122,6 @@ DYNAMIC_API PEP_STATUS update_identity( DYNAMIC_API PEP_STATUS myself(PEP_SESSION session, pEp_identity * identity); -/** - * - * - * @brief Callback being called by do_keymanagement() - * - * @param[in] management data structure to deliver (implementation defined) - * - * @retval identity to check or NULL to terminate do_keymanagement() - * if given identity must be created with new_identity() - * the identity struct is going to the ownership of this library - * it must not be freed by the callee - * - * @warning this callback has to block until an identity or NULL can be returned - * an implementation is not provided by this library; instead it has to be - * implemented by the user of this library - * - */ - -typedef pEp_identity *(*retrieve_next_identity_t)(void *management); - - -/** - * - * - * @brief Callback for appending to queue - * - * @param[in] ident identity to examine - * @param[in] management data structure to deliver (implementation defined) - * - * @retval 0 if identity was added successfully to queue or nonzero otherwise - * - * - */ - -typedef int (*examine_identity_t)(pEp_identity *ident, void *management); - - -/** - * - * - * @brief Register examine_identity() callback - * - * @param[in] session session to use - * @param[in] examine_identity examine_identity() function to register - * @param[in] management data structure to deliver (implementation defined) - * - * @retval PEP_STATUS_OK - * @retval PEP_ILLEGAL_VALUE illegal parameter values - * - */ - -DYNAMIC_API PEP_STATUS register_examine_function( - PEP_SESSION session, - examine_identity_t examine_identity, - void *management - ); - - -/** - * - * - * @brief Function to be run on an extra thread - * - * @param[in] retrieve_next_identity pointer to retrieve_next_identity() - * callback which returns at least a valid - * address field in the identity struct - * - * @retval PEP_STATUS_OK if thread has to terminate successfully - * @retval PEP_ILLEGAL_VALUE illegal parameter values - * @retval PEP_OUT_OF_MEMORY out of memory - * @retval any other value on failure - * - * @warning to ensure proper working of this library, a thread has to be started - * with this function immediately after initialization - * do_keymanagement() calls retrieve_next_identity(management) - * messageToSend can only be null if no transport is application based - * if transport system is not used it must not be NULL - * - */ - -DYNAMIC_API PEP_STATUS do_keymanagement( - retrieve_next_identity_t retrieve_next_identity, - void *management - ); - - /** * * diff --git a/src/map_asn1.c b/src/map_asn1.c index c6ba31f3..8ea479bd 100644 --- a/src/map_asn1.c +++ b/src/map_asn1.c @@ -131,8 +131,7 @@ IdentityList_t *IdentityList_from_identity_list( { bool allocated = !result; - assert(list); - if (!list) + if (!(list && list->ident)) return NULL; if (allocated) { @@ -190,3 +189,902 @@ enomem: return NULL; } +PStringPair_t *PStringPair_from_Struct( + const stringpair_t *value, + PStringPair_t *result + ) +{ + bool allocated = !result; + + assert(value); + if (!value) + return NULL; + + if (allocated) + result = (PStringPair_t *) calloc(1, sizeof(PStringPair_t)); + assert(result); + if (!result) + return NULL; + + if (value->key) { + int r = OCTET_STRING_fromBuf(&result->key, value->key, -1); + if (r) + goto enomem; + } + + if (value->value) { + int r = OCTET_STRING_fromBuf(&result->value, value->value, -1); + if (r) + goto enomem; + } + + return result; + +enomem: + if (allocated) + ASN_STRUCT_FREE(asn_DEF_PStringPair, result); + return NULL; +} + +stringpair_t *PStringPair_to_Struct(PStringPair_t *value) +{ + assert(value); + if (!value) + return NULL; + + stringpair_t *result = (stringpair_t *) calloc(1, sizeof(stringpair_t)); + assert(result); + if (!result) + goto enomem; + + result->key = strndup((char *) value->key.buf, + value->key.size); + assert(result->key); + if (!result->key) + goto enomem; + + result->value = strndup((char *) value->value.buf, + value->value.size); + assert(result->value); + if (!result->value) + goto enomem; + + return result; + +enomem: + free_stringpair(result); + return NULL; +} + +PStringPairList_t *PStringPairList_from_stringpair_list( + const stringpair_list_t *list, + PStringPairList_t *result + ) +{ + bool allocated = !result; + + assert(list); + if (!(list && list->value)) + return NULL; + + if (allocated) { + result = (PStringPairList_t *) calloc(1, sizeof(PStringPairList_t)); + assert(result); + if (!result) + return NULL; + } + else { + asn_sequence_empty(result); + } + + for (const stringpair_list_t *l = list; l && l->value; l=l->next) { + PStringPair_t *value = PStringPair_from_Struct(l->value, NULL); + if (ASN_SEQUENCE_ADD(&result->list, value)) { + ASN_STRUCT_FREE(asn_DEF_PStringPair, value); + goto enomem; + } + } + + return result; + +enomem: + if (allocated) + ASN_STRUCT_FREE(asn_DEF_PStringPairList, result); + return NULL; +} + +stringpair_list_t *PStringPairList_to_stringpair_list( + PStringPairList_t *list, + stringpair_list_t *result + ) +{ + bool allocated = !result; + + assert(list); + if (!list) + return NULL; + + if (allocated) + result = new_stringpair_list(NULL); + if (!result) + return NULL; + + stringpair_list_t *r = result; + for (int i=0; ilist.count; i++) { + stringpair_t *value = PStringPair_to_Struct(list->list.array[i]); + r = stringpair_list_add(r, value); + if (!r) + goto enomem; + } + + return result; + +enomem: + if (allocated) + free_stringpair_list(result); + return NULL; +} + +PStringList_t *PStringList_from_stringlist( + const stringlist_t *list, + PStringList_t *result + ) +{ + bool allocated = !result; + + assert(list); + if (!(list && list->value)) + return NULL; + + if (allocated) { + result = (PStringList_t *) calloc(1, sizeof(PStringList_t)); + assert(result); + if (!result) + return NULL; + } + else { + asn_sequence_empty(result); + } + + for (const stringlist_t *l = list; l && l->value; l=l->next) { + PString_t *element = (PString_t *) calloc(1, sizeof(PString_t)); + assert(element); + if (!element) + goto enomem; + + int r = OCTET_STRING_fromBuf(element, l->value, -1); + if (r) + goto enomem; + if (ASN_SEQUENCE_ADD(&result->list, element)) { + ASN_STRUCT_FREE(asn_DEF_PString, element); + goto enomem; + } + } + + return result; + +enomem: + if (allocated) + ASN_STRUCT_FREE(asn_DEF_PStringList, result); + return NULL; +} + +stringlist_t *PStringList_to_stringlist(PStringList_t *list) +{ + assert(list); + if (!list) + return NULL; + + stringlist_t *result = (stringlist_t *) calloc(1, sizeof(stringlist_t)); + assert(result); + if (!result) + goto enomem; + + stringlist_t *r = result; + + for (int i=0; ilist.count; i++) { + char *s = strndup((char *) list->list.array[i]->buf, + list->list.array[i]->size); + assert(s); + if (!s) + goto enomem; + r->value = s; + if (i < list->list.count-1) { + r->next = (stringlist_t *) calloc(1, sizeof(stringlist_t)); + assert(r->next); + if (!r->next) + goto enomem; + r = r->next; + } + } + + return result; + +enomem: + free_stringlist(result); + return NULL; +} + +PBlobList_t *PBlobList_from_bloblist( + bloblist_t *list, + PBlobList_t *result, + bool copy, + size_t max_blob_size + ) +{ + bool allocated = !result; + if (!max_blob_size) + max_blob_size = SIZE_MAX; + + assert(list); + if (!(list && list->value)) + return NULL; + + if (allocated) { + result = (PBlobList_t *) calloc(1, sizeof(PBlobList_t)); + assert(result); + if (!result) + return NULL; + } + else { + asn_sequence_empty(result); + } + + size_t rest_blob_size = max_blob_size; + + for (bloblist_t *l = list; l && l->value; l=l->next) { + PBlob_t *element = (PBlob_t *) calloc(1, sizeof(PBlob_t)); + assert(element); + if (!element) + goto enomem; + + int r = 0; + + if (l->size > rest_blob_size) + goto enomem; + rest_blob_size -= l->size; + + if (copy) { + r = OCTET_STRING_fromBuf(&element->value, l->value, l->size); + if (r) + goto enomem; + } + else /* move */ { +#if defined(__CHAR_BIT__) && __CHAR_BIT__ == 8 + element->value.buf = (uint8_t *) l->value; +#else + // FIXME: this is problematic on platforms with bytes != octets + // we want this warning + element->value.buf = l->value; +#endif + l->value = NULL; + element->value.size = l->size; + l->size = 0; + } + + if (!EMPTYSTR(l->mime_type)) { + PString_t *_mime_type = (PString_t *) calloc(1, sizeof(PString_t)); + assert(_mime_type); + if (!_mime_type) + goto enomem; + + r = OCTET_STRING_fromBuf(_mime_type, l->mime_type, -1); + if (r) + goto enomem; + element->mime_type = _mime_type; + } + + if (!EMPTYSTR(l->filename)) { + PString_t *_filename = (PString_t *) calloc(1, sizeof(PString_t)); + assert(_filename); + if (!_filename) + goto enomem; + + r = OCTET_STRING_fromBuf(_filename, l->filename, -1); + if (r) + goto enomem; + element->filename = _filename; + } + + switch (l->disposition) { + case PEP_CONTENT_DISP_ATTACHMENT: + element->disposition = ContentDisposition_attachment; + break; + case PEP_CONTENT_DISP_INLINE: + element->disposition = ContentDisposition_inline; + break; + case PEP_CONTENT_DISP_OTHER: + element->disposition = ContentDisposition_other; + break; + default: + assert(0); // should not happen; use default + element->disposition = ContentDisposition_attachment; + } + + if (ASN_SEQUENCE_ADD(&result->list, element)) { + ASN_STRUCT_FREE(asn_DEF_PBlob, element); + goto enomem; + } + } + + return result; + +enomem: + if (allocated) + ASN_STRUCT_FREE(asn_DEF_PBlobList, result); + return NULL; +} + +bloblist_t *PBlobList_to_bloblist( + PBlobList_t *list, + bloblist_t *result, + bool copy, + size_t max_blob_size + ) +{ + bool allocated = !result; + if (!max_blob_size) + max_blob_size = SIZE_MAX; + + assert(list); + if (!list) + return NULL; + + if (allocated) + result = new_bloblist(NULL, 0, NULL, NULL); + if (!result) + return NULL; + + size_t rest_blob_size = max_blob_size; + + bloblist_t *r = result; + for (int i=0; ilist.count; i++) { + // this should not happen + assert(list->list.array[i]); + if (!list->list.array[i]) + goto enomem; + + if (list->list.array[i]->value.size > rest_blob_size) + goto enomem; + rest_blob_size -= list->list.array[i]->value.size; + + char *_mime_type = NULL; + if (list->list.array[i]->mime_type) { + _mime_type = strndup((char *) list->list.array[i]->mime_type->buf, + list->list.array[i]->mime_type->size); + assert(_mime_type); + if (!_mime_type) + goto enomem; + } + + char *_filename = NULL; + if (list->list.array[i]->filename) { + _filename = strndup((char *) list->list.array[i]->filename->buf, + list->list.array[i]->filename->size); + assert(_filename); + if (!_filename) + goto enomem; + } + +#if defined(__CHAR_BIT__) && __CHAR_BIT__ == 8 + char *_data = (char *) list->list.array[i]->value.buf; +#else + // FIXME: this is problematic on platforms with bytes != octets + // we want this warning + char *_data = list->list.array[i]->value.buf; +#endif + + if (copy) { + _data = strndup(_data, list->list.array[i]->value.size); + assert(_data); + if (!_data) + goto enomem; + } + + // bloblist_add() has move semantics + r = bloblist_add(r, _data, list->list.array[i]->value.size, _mime_type, + _filename); + + if (!copy) { + list->list.array[i]->value.buf = NULL; + list->list.array[i]->value.size = 0; + } + + if (!r) + goto enomem; + + switch (list->list.array[i]->disposition) { + case ContentDisposition_attachment: + r->disposition = PEP_CONTENT_DISP_ATTACHMENT; + break; + case ContentDisposition_inline: + r->disposition = PEP_CONTENT_DISP_INLINE; + break; + case ContentDisposition_other: + r->disposition = PEP_CONTENT_DISP_OTHER; + break; + default: + assert(0); // should not happen; use default + r->disposition = PEP_CONTENT_DISP_ATTACHMENT; + } + } + + return result; + +enomem: + if (allocated) + free_bloblist(result); + return NULL; +} + +ASN1Message_t *ASN1Message_from_message( + message *msg, + ASN1Message_t *result, + bool copy, + size_t max_blob_size + ) +{ + bool allocated = !result; + if (!max_blob_size) + max_blob_size = SIZE_MAX; + + assert(msg); + if (!msg) + return NULL; + + if (allocated) { + result = (ASN1Message_t *) calloc(1, sizeof(ASN1Message_t)); + assert(result); + if (!result) + return NULL; + } + else { + asn_sequence_empty(result); + } + + // direction will be skipped on the line + + if (!EMPTYSTR(msg->id)) { + PString_t *str = (PString_t *) calloc(1, sizeof(PString_t)); + assert(str); + if (!str) + goto enomem; + + int r = OCTET_STRING_fromBuf(str, msg->id, -1); + if (r) + goto enomem; + + result->id = str; + } + + if (msg->sent) { + GeneralizedTime_t *ts = asn_time2GT(NULL, msg->sent, 1); + if (!ts) + goto enomem; + + result->sent = ts; + } + + if (msg->recv) { + GeneralizedTime_t *ts = asn_time2GT(NULL, msg->recv, 1); + if (!ts) + goto enomem; + + result->recv = ts; + } + + if (!msg->from) // from is not optional + goto enomem; + Identity_from_Struct(msg->from, &result->from); + + if (msg->to && msg->to->ident) { + IdentityList_t *l = IdentityList_from_identity_list(msg->to, NULL); + if (!l) + goto enomem; + + result->to = l; + } + + if (msg->cc && msg->cc->ident) { + IdentityList_t *l = IdentityList_from_identity_list(msg->cc, NULL); + if (!l) + goto enomem; + + result->cc = l; + } + + if (msg->bcc && msg->bcc->ident) { + IdentityList_t *l = IdentityList_from_identity_list(msg->bcc, NULL); + if (!l) + goto enomem; + + result->bcc = l; + } + + if (msg->recv_by) { + Identity_t *i = Identity_from_Struct(msg->recv_by, NULL); + if (!i) + goto enomem; + + result->recv_by = i; + } + + if (msg->reply_to && msg->reply_to->ident) { + IdentityList_t *l = IdentityList_from_identity_list(msg->reply_to, NULL); + if (!l) + goto enomem; + + result->reply_to = l; + } + + if (msg->in_reply_to && msg->in_reply_to->value) { + PStringList_t *l = PStringList_from_stringlist(msg->in_reply_to, NULL); + if (!l) + goto enomem; + + result->in_reply_to = l; + } + + if (msg->references && msg->references->value) { + PStringList_t *l = PStringList_from_stringlist(msg->references, NULL); + if (!l) + goto enomem; + + result->references = l; + } + + if (msg->keywords && msg->keywords->value) { + PStringList_t *l = PStringList_from_stringlist(msg->keywords, NULL); + if (!l) + goto enomem; + + result->keywords = l; + } + + if (!EMPTYSTR(msg->comments)) { + PString_t *str = (PString_t *) calloc(1, sizeof(PString_t)); + assert(str); + if (!str) + goto enomem; + + int r = OCTET_STRING_fromBuf(str, msg->comments, -1); + if (r) + goto enomem; + + result->comments = str; + } + + if (msg->opt_fields && msg->opt_fields->value) { + PStringPairList_t *l = PStringPairList_from_stringpair_list(msg->opt_fields, NULL); + if (!l) + goto enomem; + + result->opt_fields = l; + } + + if (!EMPTYSTR(msg->_sender_fpr)) { + Hash_t *str = (Hash_t *) calloc(1, sizeof(Hash_t)); + assert(str); + if (!str) + goto enomem; + + int r = OCTET_STRING_fromBuf(str, msg->_sender_fpr, -1); + if (r) + goto enomem; + + result->sender_fpr = str; + } + + if (!EMPTYSTR(msg->shortmsg)) { + PString_t *str = (PString_t *) calloc(1, sizeof(PString_t)); + assert(str); + if (!str) + goto enomem; + + int r = OCTET_STRING_fromBuf(str, msg->shortmsg, -1); + if (r) + goto enomem; + + result->shortmsg = str; + } + + size_t rest_blob_size = max_blob_size; + + if (!EMPTYSTR(msg->longmsg)) { + PString_t *str = (PString_t *) calloc(1, sizeof(PString_t)); + assert(str); + if (!str) + goto enomem; + + if (copy) { + int r = OCTET_STRING_fromBuf(str, msg->longmsg, -1); + if (r) + goto enomem; + if (str->size > rest_blob_size) + goto enomem; + } + else /* move */ { + str->size = strlen(msg->longmsg); + if (str->size > rest_blob_size) + goto enomem; + + str->buf = (uint8_t *) msg->longmsg; + msg->longmsg = NULL; + } + + rest_blob_size -= str->size; + result->longmsg = str; + } + + if (!EMPTYSTR(msg->longmsg_formatted)) { + PString_t *str = (PString_t *) calloc(1, sizeof(PString_t)); + assert(str); + if (!str) + goto enomem; + + if (copy) { + int r = OCTET_STRING_fromBuf(str, msg->longmsg_formatted, -1); + if (r) + goto enomem; + if (str->size > rest_blob_size) + goto enomem; + } + else /* move */ { + str->size = strlen(msg->longmsg_formatted); + if (str->size > rest_blob_size) + goto enomem; + + str->buf = (uint8_t *) msg->longmsg_formatted; + msg->longmsg_formatted = NULL; + } + + rest_blob_size -= str->size; + result->longmsg_formatted = str; + } + + if (msg->attachments && msg->attachments->value) { + PBlobList_t *bl = PBlobList_from_bloblist(msg->attachments, NULL, copy, + rest_blob_size); + if (!bl) + goto enomem; + result->attachments = bl; + } + + return result; + +enomem: + if (allocated) + ASN_STRUCT_FREE(asn_DEF_ASN1Message, result); + return NULL; +} + +message *ASN1Message_to_message( + ASN1Message_t *msg, + message *result, + bool copy, + size_t max_blob_size + ) +{ + bool allocated = !result; + if (!max_blob_size) + max_blob_size = SIZE_MAX; + + assert(msg); + if (!msg) + return NULL; + + if (allocated) { + result = new_message(PEP_dir_incoming); + if (!result) + goto enomem; + } + + if (msg->direction) { + switch (*msg->direction) { + case MessageDirection_incoming: + result->dir = PEP_dir_incoming; + break; + case MessageDirection_outgoing: + result->dir = PEP_dir_outgoing; + break; + default: + assert(0); + } + } + + if (msg->id) { + result->id = strndup((char *) msg->id->buf, msg->id->size); + assert(result->id); + if (!result->id) + goto enomem; + } + + if (msg->sent) { + timestamp *_sent = new_timestamp(0); + if (!_sent) + goto enomem; + + if (asn_GT2time(msg->sent, _sent, 1) == -1) + goto enomem; + result->sent = _sent; + } + + if (msg->recv) { + timestamp *_recv = new_timestamp(0); + if (!_recv) + goto enomem; + + if (asn_GT2time(msg->recv, _recv, 1) == -1) + goto enomem; + result->recv = _recv; + } + + // from is mandatory + result->from = Identity_to_Struct(&msg->from, NULL); + if (!result->from) + goto enomem; + + if (msg->to) { + identity_list *il = IdentityList_to_identity_list(msg->to, NULL); + if (!il) + goto enomem; + + result->to = il; + } + + if (msg->cc) { + identity_list *il = IdentityList_to_identity_list(msg->cc, NULL); + if (!il) + goto enomem; + + result->cc = il; + } + + if (msg->bcc) { + identity_list *il = IdentityList_to_identity_list(msg->bcc, NULL); + if (!il) + goto enomem; + + result->bcc = il; + } + + if (msg->recv_by) { + pEp_identity *i = Identity_to_Struct(msg->recv_by, NULL); + if (!i) + goto enomem; + + result->recv_by = i; + } + + if (msg->reply_to) { + identity_list *il = IdentityList_to_identity_list(msg->reply_to, NULL); + if (!il) + goto enomem; + + result->reply_to = il; + } + + if (msg->in_reply_to) { + stringlist_t *l = PStringList_to_stringlist(msg->in_reply_to); + if (!l) + goto enomem; + + result->in_reply_to = l; + } + + if (msg->references) { + stringlist_t *l = PStringList_to_stringlist(msg->references); + if (!l) + goto enomem; + + result->references = l; + } + + if (msg->keywords) { + stringlist_t *l = PStringList_to_stringlist(msg->keywords); + if (!l) + goto enomem; + + result->keywords = l; + } + + if (msg->comments) { + char *s = strndup((char *) msg->comments->buf, msg->comments->size); + assert(s); + if (!s) + goto enomem; + + result->comments = s; + } + + if (msg->opt_fields) { + stringpair_list_t *l = + PStringPairList_to_stringpair_list(msg->opt_fields, NULL); + if (!l) + goto enomem; + + result->opt_fields = l; + } + + if (msg->sender_fpr) { + char *_sender_fpr = strndup((char *) msg->sender_fpr->buf, + msg->sender_fpr->size); + if (!_sender_fpr) + goto enomem; + + result->_sender_fpr = _sender_fpr; + } + + if (msg->shortmsg) { + char *s = strndup((char *) msg->shortmsg->buf, msg->shortmsg->size); + assert(s); + if (!s) + goto enomem; + + result->shortmsg = s; + } + + size_t rest_blob_size = max_blob_size; + + if (msg->longmsg) { + if (msg->longmsg->size > rest_blob_size) + goto enomem; + + char *s = NULL; + + if (copy) { + s = strndup((char *) msg->longmsg->buf, msg->longmsg->size); + if (!s) + goto enomem; + rest_blob_size -= msg->longmsg->size; + } + else /* move */ { + s = (char *) msg->longmsg->buf; + msg->longmsg->buf = NULL; + rest_blob_size -= msg->longmsg->size; + msg->longmsg->size = 0; + } + + result->longmsg = s; + } + + if (msg->longmsg_formatted) { + if (msg->longmsg_formatted->size > rest_blob_size) + goto enomem; + + char *s = NULL; + + if (copy) { + s = strndup((char *) msg->longmsg_formatted->buf, + msg->longmsg_formatted->size); + if (!s) + goto enomem; + rest_blob_size -= msg->longmsg_formatted->size; + } + else /* move */ { + s = (char *) msg->longmsg_formatted->buf; + msg->longmsg_formatted->buf = NULL; + rest_blob_size -= msg->longmsg_formatted->size; + msg->longmsg_formatted->size = 0; + } + + result->longmsg_formatted = s; + } + + if (msg->attachments) { + bloblist_t *a = PBlobList_to_bloblist(msg->attachments, NULL, copy, + rest_blob_size); + if (!a) + goto enomem; + + result->attachments = a; + } + + return result; + +enomem: + if (allocated) + free_message(result); + return NULL; +} + diff --git a/src/map_asn1.h b/src/map_asn1.h index d13ccd92..e9aa35ff 100644 --- a/src/map_asn1.h +++ b/src/map_asn1.h @@ -7,10 +7,8 @@ #ifndef MAP_ASN1_H #define MAP_ASN1_H -#include "pEpEngine.h" -#include "identity_list.h" -#include "../asn.1/Identity.h" -#include "../asn.1/IdentityList.h" +#include "message.h" +#include "ASN1Message.h" #ifdef __cplusplus extern "C" { @@ -89,6 +87,217 @@ IdentityList_t *IdentityList_from_identity_list( identity_list *IdentityList_to_identity_list(IdentityList_t *list, identity_list *result); + +/** + * + * + * @brief Convert stringpair_t into ASN.1 PStringPair_t + * + * @param value[in] stringpair_t to convert + * @param result[in,out] PStringPair_t to update or NULL to alloc a new one + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +PStringPair_t *PStringPair_from_Struct( + const stringpair_t *value, + PStringPair_t *result + ); + + +/** + * + * + * @brief Convert ASN.1 PStringPair_t into stringpair_t + * + * @param value[in] PStringPair_t to convert + * + * @retval pointer to updated or allocated result + * + * @warning a new struct is allocated, the ownership goes to the caller + * + */ + +stringpair_t *PStringPair_to_Struct(PStringPair_t *value); + + +/** + * + * + * @brief Convert stringpair_list_t into ASN.1 PStringPairList_t + * + * @param list[in] stringpair_list to convert + * @param result[inout] PStringPairList_t to update or NULL to alloc a new one + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +PStringPairList_t *PStringPairList_from_stringpair_list( + const stringpair_list_t *list, + PStringPairList_t *result + ); + +/** + * + * + * @brief Convert ASN.1 PStringPairList_t to stringpair_list_t + * + * @param list[in] ASN.1 PStringPairList_t to convert + * @param result[inout] stringpair_list_t to update or NULL to alloc a new one + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +stringpair_list_t *PStringPairList_to_stringpair_list( + PStringPairList_t *list, + stringpair_list_t *result + ); + + +/** + * + * + * @brief Convert stringlist_t into ASN.1 PStringList_t + * + * @param list[in] stringlist to convert + * @param result[inout] PStringList_t to update or NULL to alloc a new one + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +PStringList_t *PStringList_from_stringlist( + const stringlist_t *list, + PStringList_t *result + ); + +/** + * + * + * @brief Convert ASN.1 PStringList_t to stringlist_t + * + * @param list[in] ASN.1 PStringList_t to convert + * + * @retval pointer to updated or allocated result + * + * @warning a new struct is allocated, the ownership goes to the caller + * + */ + +stringlist_t *PStringList_to_stringlist(PStringList_t *list); + + +/** + * + * + * @brief Convert bloblist_t into ASN.1 PBlobList_t + * + * @param list[in] bloblist to convert + * @param result[inout] PBlobList_t to update or NULL to alloc a new one + * @param copy copy data if true, move data otherwise + * @param max_blob_size reject if sum(blob.size) > max_blob_size + * to disable set to 0 + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +PBlobList_t *PBlobList_from_bloblist( + bloblist_t *list, + PBlobList_t *result, + bool copy, + size_t max_blob_size + ); + + +/** + * + * + * @brief Convert ASN.1 PBlobList_t to bloblist_t + * + * @param list[in] ASN.1 PBlobList_t to convert + * @param result[inout] bloblist_t to update or NULL to alloc a new one + * @param copy copy data if true, move data otherwise + * @param max_blob_size reject if sum(blob.size) > max_blob_size + * to disable set to 0 + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +bloblist_t *PBlobList_to_bloblist( + PBlobList_t *list, + bloblist_t *result, + bool copy, + size_t max_blob_size + ); + + +/** + * + * + * @brief Convert message into ASN.1 ASN1Message_t + * + * @param msg[in] message to convert + * @param result[inout] ASN1Message_t to update or NULL to alloc a new one + * @param copy copy data if true, move data otherwise + * @param max_blob_size reject if sum(blob.size) > max_blob_size + * to disable set to 0 + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +ASN1Message_t *ASN1Message_from_message( + message *msg, + ASN1Message_t *result, + bool copy, + size_t max_blob_size + ); + + +/** + * + * + * @brief Convert ASN.1 ASN1Message_t to message + * + * @param msg[in] ASN.1 ASN1Message_t to convert + * @param result[inout] message to update or NULL to alloc a new one + * @param copy copy data if true, move data otherwise + * @param max_blob_size reject if sum(blob.size) > max_blob_size + * to disable set to 0 + * + * @retval pointer to updated or allocated result + * + * @warning if a new struct is allocated, the ownership goes to the caller + * + */ + +message *ASN1Message_to_message( + ASN1Message_t *msg, + message *result, + bool copy, + size_t max_blob_size + ); + + #ifdef __cplusplus } #endif diff --git a/src/message_api.c b/src/message_api.c index e2e3c8ea..4802a9bf 100644 --- a/src/message_api.c +++ b/src/message_api.c @@ -775,11 +775,19 @@ static PEP_STATUS generate_message_id(message* msg) { size_t buf_len = 2; // NUL + @ char* from_addr = msg->from->address; - char* domain_ptr = strstr(from_addr, "@"); - if (!domain_ptr || *(domain_ptr + 1) == '\0') - domain_ptr = "localhost"; - else - domain_ptr++; + + /* Look for the *last* occurrence of '@' within the string beginning at + from_addr; if no '@' exists or if it is at the very end then keep the + "localhost" default as domain. */ + char *p; + char *domain_ptr = "localhost"; + for (p = from_addr + strlen (from_addr); p >= from_addr; p --) + if (* p == '@') + { + if (p [1] != '\0') + domain_ptr = p + 1; + break; + } buf_len += strlen(domain_ptr); diff --git a/src/message_codec.c b/src/message_codec.c new file mode 100644 index 00000000..ba8d6df5 --- /dev/null +++ b/src/message_codec.c @@ -0,0 +1,141 @@ +/** + * @file ASN1Message_codec.c + * @brief Implementation for ASN1Message encode and decode functions which transform message payloads to + * and from PER-encoded data, and XER text to and from PER + * + * @see https://www.itu.int/en/ITU-T/asn1/Pages/introduction.aspx + * + * @license GNU General Public License 3.0 - see LICENSE.txt + */ + +#include "platform.h" + +#include "distribution_codec.h" +#include "../asn.1/ASN1Message.h" +#include "pEp_internal.h" +#include "growing_buf.h" + +DYNAMIC_API PEP_STATUS decode_ASN1Message_message( + const char *data, + size_t size, + ASN1Message_t **msg + ) +{ + assert(data && msg); + if (!(data && msg)) + return PEP_ILLEGAL_VALUE; + + *msg = NULL; + ASN1Message_t *_msg = NULL; + uper_decode_complete(NULL, &asn_DEF_ASN1Message, (void **) &_msg, data, size); + if (!_msg) + return PEP_PEPMESSAGE_ILLEGAL_MESSAGE; + + *msg = _msg; + return PEP_STATUS_OK; +} + +PEP_STATUS encode_ASN1Message_message( + ASN1Message_t *msg, + char **data, + size_t *size + ) +{ + assert(data && msg); + if (!(data && msg)) + return PEP_ILLEGAL_VALUE; + + *data = NULL; + *size = 0; + + char *_data = NULL; + ssize_t _size = uper_encode_to_new_buffer(&asn_DEF_ASN1Message, NULL, msg, + (void **) &_data); + if (_size == -1) + return PEP_CANNOT_ENCODE; + + *data = _data; + *size = (size_t) _size; + + return PEP_STATUS_OK; +} + +PEP_STATUS PER_to_XER_ASN1Message_msg( + const char *data, + size_t size, + char **text + ) +{ + PEP_STATUS status = PEP_STATUS_OK; + growing_buf_t *dst = NULL; + + assert(data && text); + if (!(data && text)) + return PEP_ILLEGAL_VALUE; + + *text = NULL; + + ASN1Message_t *msg = NULL; + status = decode_ASN1Message_message(data, size, &msg); + if (status) + goto the_end; + + dst = new_growing_buf(); + if (!dst) { + status = PEP_OUT_OF_MEMORY; + goto the_end; + } + + asn_enc_rval_t er = xer_encode(&asn_DEF_ASN1Message, msg, XER_F_BASIC, + (asn_app_consume_bytes_f *) growing_buf_consume, (void *) dst); + if (er.encoded == -1) { + status = PEP_CANNOT_ENCODE; + goto the_end; + } + + *text = dst->data; + dst->data = NULL; + +the_end: + free_growing_buf(dst); + ASN_STRUCT_FREE(asn_DEF_ASN1Message, msg); + return status; +} + +PEP_STATUS XER_to_PER_ASN1Message_msg( + const char *text, + char **data, + size_t *size + ) +{ + PEP_STATUS status = PEP_STATUS_OK; + + assert(text && data && size); + if (!(text && data && size)) + return PEP_ILLEGAL_VALUE; + + *data = NULL; + *size = 0; + + ASN1Message_t *msg = NULL; + asn_dec_rval_t dr = xer_decode(NULL, &asn_DEF_ASN1Message, (void **) &msg, + (const void *) text, strlen(text)); + if (dr.code != RC_OK) { + status = PEP_PEPMESSAGE_ILLEGAL_MESSAGE; + goto the_end; + } + + char *_data = NULL; + size_t _size = 0; + status = encode_ASN1Message_message(msg, &_data, &_size); + if (status) + goto the_end; + + *data = _data; + *size = (size_t) _size; + +the_end: + ASN_STRUCT_FREE(asn_DEF_ASN1Message, msg); + return status; +} + diff --git a/src/message_codec.h b/src/message_codec.h new file mode 100644 index 00000000..059e9088 --- /dev/null +++ b/src/message_codec.h @@ -0,0 +1,103 @@ +/** + * @file ASN1Message_codec.h + * @brief Definitions for ASN1Message encode and decode functions which transform message payloads to + * and from PER-encoded data, and XER text to and from PER + * + * @see https://www.itu.int/en/ITU-T/asn1/Pages/introduction.aspx + * + * @license GNU General Public License 3.0 - see LICENSE.txt + */ + + +#ifndef PEPMESSAGE_CODEC_H +#define PEPMESSAGE_CODEC_H + +#include "pEpEngine.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +struct ASN1Message; + +/** + * + * + * @brief decode PER encoded ASN1Message message + * + * @param[in] data PER encoded data + * @param[in] size size of PER encoded data + * @param[out] msg decoded ASN1Message message + * + * @retval status + * + * @ownership msg goes into the ownership of the caller + */ +DYNAMIC_API PEP_STATUS decode_ASN1Message_message( + const char *data, + size_t size, + struct ASN1Message **msg + ); + +/** + * + * + * @brief decode PER encoded ASN1Message message + * + * @param[in] msg ASN1Message message to encode + * @param[out] data PER encoded data + * @param[out] size size of PER encoded data + * + * @retval status + * + * @ownership msg goes into the ownership of the caller + */ +DYNAMIC_API PEP_STATUS encode_ASN1Message_message( + struct ASN1Message *msg, + char **data, + size_t *size + ); + + +/** + * + * + * @brief decode ASN1Message message from PER into XER + * + * @param[in] data PER encoded data + * @param[in] size size of PER encoded data + * @param[out] text XER text of the same ASN1Message message + * + * @retval status + */ +DYNAMIC_API PEP_STATUS PER_to_XER_ASN1Message_msg( + const char *data, + size_t size, + char **text + ); + +/** + * + * + * @brief encode ASN1Message message from XER into PER + * + * @param[in] text string text with XER text of the ASN1Message message + * @param[out] data PER encoded data + * @param[out] size size of PER encoded data + * + * @retval status + */ +DYNAMIC_API PEP_STATUS XER_to_PER_ASN1Message_msg( + const char *text, + char **data, + size_t *size + ); + + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/src/pEpEngine.c b/src/pEpEngine.c index 1dac1961..94636964 100644 --- a/src/pEpEngine.c +++ b/src/pEpEngine.c @@ -28,6 +28,13 @@ DYNAMIC_API PEP_STATUS init( { PEP_STATUS status = PEP_STATUS_OK; + // Initialise the path cache. It is the state of the environment at this + // time that determines path names, unless the path cache is explicitly + // reset later. + status = reset_path_cache (); + if (status != PEP_STATUS_OK) + return status; + bool in_first = false; assert(sqlite3_threadsafe()); @@ -143,6 +150,9 @@ DYNAMIC_API void release(PEP_SESSION session) if (session) { free_Sync_state(session); + // Clear the path cache, releasing a little memory. + clear_path_cache (); + if (session->db) { pEp_finalize_sql_stmts(session); if (session->db) { @@ -161,6 +171,12 @@ DYNAMIC_API void release(PEP_SESSION session) sqlite3_close_v2(session->system_db); } + if (!EMPTYSTR(session->curr_passphrase)) { + free (session->curr_passphrase); + /* In case the following freeing code still uses the field. */ + session->curr_passphrase = NULL; + } + release_transport_system(session, out_last); release_cryptotech(session, out_last); free(session); diff --git a/src/pEpEngine.h b/src/pEpEngine.h index 0ea84d0b..1aa94503 100644 --- a/src/pEpEngine.h +++ b/src/pEpEngine.h @@ -29,7 +29,7 @@ extern "C" { #define PEP_ENGINE_VERSION_MAJOR 3 #define PEP_ENGINE_VERSION_MINOR 2 #define PEP_ENGINE_VERSION_PATCH 0 -#define PEP_ENGINE_VERSION_RC 1 +#define PEP_ENGINE_VERSION_RC 5 #define PEP_OWN_USERID "pEp_own_userId" @@ -160,12 +160,30 @@ typedef enum { PEP_DISTRIBUTION_ILLEGAL_MESSAGE = 0x1002, PEP_STORAGE_ILLEGAL_MESSAGE = 0x1102, + PEP_PEPMESSAGE_ILLEGAL_MESSAGE = 0x1202, + + // transport cannot init at all + PEP_TRANSPORT_CANNOT_INIT = 0x2000, + + // transport can init recv but not send + PEP_TRANSPORT_CANNOT_INIT_SEND = 0x2001, + + // transport can init send but not recv + PEP_TRANSPORT_CANNOT_INIT_RECV = 0x2002, + + // transport init good but temporary down + PEP_TRANSPORT_DOWN = 0x2003, + + // general error in transport + PEP_TRANSPORT_ERROR = 0x20ff, PEP_COMMIT_FAILED = 0xff01, PEP_MESSAGE_CONSUME = 0xff02, PEP_MESSAGE_IGNORE = 0xff03, PEP_CANNOT_CONFIG = 0xff04, + PEP_UNBOUND_ENVIRONMENT_VARIABLE = -8, + PEP_PATH_SYNTAX_ERROR = -7, PEP_RECORD_NOT_FOUND = -6, PEP_CANNOT_CREATE_TEMP_FILE = -5, PEP_ILLEGAL_VALUE = -4, @@ -173,7 +191,7 @@ typedef enum { PEP_OUT_OF_MEMORY = -2, PEP_UNKNOWN_ERROR = -1, - PEP_VERSION_MISMATCH = -7, + PEP_VERSION_MISMATCH = -9, } PEP_STATUS; /** @@ -1741,10 +1759,30 @@ DYNAMIC_API PEP_STATUS is_pEp_user(PEP_SESSION session, pEp_identity *identity, bool* is_pEp); + /** + * + * + * @brief Returns the directory for pEp management db as a relative + * path from the home directory (or the pEp home directory) + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. + * + * @retval char* relative pathname + * @retval NULL on failure + * + */ + +DYNAMIC_API const char *per_user_relative_directory(void); + + /** * * - * @brief Returns the directory for pEp management db + * @brief Returns the directory for pEp management db. + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. * * @retval char* path to actual per user directory * @retval NULL on failure @@ -1759,6 +1797,9 @@ DYNAMIC_API const char *per_user_directory(void); * * * @brief Returns the directory for pEp system db + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. * * @retval char* path to actual per machine directory * @retval NULL on failure @@ -1933,6 +1974,35 @@ DYNAMIC_API PEP_STATUS get_replacement_fpr( */ DYNAMIC_API PEP_STATUS set_as_pEp_user(PEP_SESSION session, pEp_identity* user); +/** + * + * + * @brief Recompute pathnames according to the current value of the + * environment. This is automatically called by init on + * platforms where a pathname cache exists, but it is possible + * to call it again explicitly in case the paths need to be + * recomputed from updated environment variables; the intended + * use case is test suites, working with temporary + * directories. + * + * @retval PEP_STATUS_OK success + * @retval PEP_UNBOUND_ENVIRONMENT_VARIABLE unknown variable referenced + * @retval PEP_PATH_SYNTAX_ERROR invalid syntax in argument + * @retval PEP_OUT_OF_MEMORY out of memory + * + */ +DYNAMIC_API PEP_STATUS reset_path_cache(void); + + /** + * + * + * @brief Empty the path cache, releasing resources. This may invalidate + * the memory used by the results of per_user_relative_directory, + * per_user_directory, per_machine_directory, android_system_db, + * unix_system_db, unix_local_db. + * + */ +DYNAMIC_API void clear_path_cache(void); #ifdef __cplusplus } diff --git a/src/pEp_internal.h b/src/pEp_internal.h index 747b92af..38ff0c22 100644 --- a/src/pEp_internal.h +++ b/src/pEp_internal.h @@ -79,12 +79,7 @@ #define _POSIX_C_SOURCE 200809L #endif #include -#ifdef NDEBUG #define LOCAL_DB unix_local_db() -#else -#define LOCAL_DB unix_local_db(false) -#define LOCAL_DB_RESET unix_local_db(true) -#endif #ifdef ANDROID #define SYSTEM_DB android_system_db() #else @@ -295,8 +290,6 @@ struct _pEpSession { sqlite3_stmt *add_userid_alias; // callbacks - examine_identity_t examine_identity; - void *examine_management; notifyHandshake_t notifyHandshake; inject_sync_event_t inject_sync_event; retrieve_next_sync_event_t retrieve_next_sync_event; diff --git a/src/pgp_sequoia.c b/src/pgp_sequoia.c index e1e969c6..95807f07 100644 --- a/src/pgp_sequoia.c +++ b/src/pgp_sequoia.c @@ -451,28 +451,24 @@ PEP_STATUS pgp_init(PEP_SESSION session, bool in_first) | SQLITE_OPEN_PRIVATECACHE, NULL); #else - // Create the home directory. - char *home_env = NULL; -#ifndef NDEBUG - home_env = getenv("PEP_HOME"); -#endif - -#define PEP_KEYS_PATH "/.pEp/keys.db" - - if (!home_env) - home_env = getenv("HOME"); - if (!home_env) - ERROR_OUT(NULL, PEP_INIT_CRYPTO_LIB_INIT_FAILED, "HOME unset"); + // Compute a string containing the DB absolute path name. +#define PEP_KEYS_RELATIVE_FILENAME "keys.db" + const char *directory = per_user_directory(); // Create the DB and initialize it. - size_t path_size = strlen(home_env) + sizeof(PEP_KEYS_PATH); + size_t path_size + = (strlen(directory) + + 1 /* '/' */ + + strlen(PEP_KEYS_RELATIVE_FILENAME) + + 1 /* '\0' */); char *path = (char *) calloc(path_size, 1); assert(path); if (!path) ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory"); - int r = snprintf(path, path_size, "%s" PEP_KEYS_PATH, home_env); + int r = snprintf(path, path_size, "%s/%s", + directory, PEP_KEYS_RELATIVE_FILENAME); assert(r >= 0 && r < path_size); if (r < 0) { free(path); diff --git a/src/platform_unix.c b/src/platform_unix.c index 153bb030..81a2cf8d 100644 --- a/src/platform_unix.c +++ b/src/platform_unix.c @@ -27,6 +27,7 @@ #include #include +#include "pEpEngine.h" /* For PEP_STATUS */ #include "platform_unix.h" #include "dynamic_api.h" @@ -137,29 +138,28 @@ long int random(void) return nrand48(xsubi); } */ -const char *android_system_db(void) +/* This is a non-caching function: see the comments in "Internal path caching + functionality" below. */ +static char *_android_system_db(void) { - static char buffer[MAX_PATH]; - static bool done = false; - - if (!done) { - char *tw_env; - if(tw_env = getenv("TRUSTWORDS")){ - char *p = stpncpy(buffer, tw_env, MAX_PATH); - ssize_t len = MAX_PATH - (p - buffer) - 2; - - if (len < strlen(SYSTEM_DB_FILENAME)) { - assert(0); - return NULL; - } + char *buffer = malloc (MAX_PATH); + if (buffer == NULL) + return NULL; + + char *tw_env; + if(tw_env = getenv("TRUSTWORDS")){ + char *p = stpncpy(buffer, tw_env, MAX_PATH); + ssize_t len = MAX_PATH - (p - buffer) - 2; - *p++ = '/'; - strncpy(p, SYSTEM_DB_FILENAME, len); - done = true; - }else{ + if (len < strlen(SYSTEM_DB_FILENAME)) { + assert(0); return NULL; } + *p++ = '/'; + strncpy(p, SYSTEM_DB_FILENAME, len); + }else{ + return NULL; } return buffer; } @@ -405,7 +405,386 @@ static void _move(const char *o, const char *ext, const char *n) free(_new); } -#ifndef NDEBUG +/** + * @internal + * + * + * + * @brief Return a malloc-allocated copy of the given string, or (this + * is the added functionality with respect to the standard + * strdup) a malloc-allocated copy of "" if the argument is + * NULL. + * Return NULL only in case of an out-of-memory error. + * + * @param[in] *original constchar + * @retval NULL out of memory + * @retval non-NULL malloc-allocated buffer + */ +static char *_strdup_or_NULL(const char *original) +{ + if (original == NULL) + original = ""; + return strdup (original); +} + + +/* + * Environment variable expansion + * ********************************************************************** + */ + +/* The state of a DFA implementing variable recognition in _expand_variables , + below. */ +enum _expand_variable_state { + _expand_variable_state_non_variable, + _expand_variable_state_after_dollar, + _expand_variable_state_after_backslash, + _expand_variable_state_in_variable +}; + +/** + * @internal + * + * + * + * @brief Set a malloc-allocated '\0'-terminated string which is + * a copy of the argument with shell variables expanded, where + * variable references use Unix shell-style syntax $VARIABLE. + * Notice that the alternative syntax ${VARIABLE} is not + * supported. + * See [FIXME: deployment-engineer documentation]. + * + * @param[in] string_with_variables char * + * @param[out] copy_with_variables_expanded char ** + * @retval PEP_STATUS_OK success + * @retval PEP_UNBOUND_ENVIRONMENT_VARIABLE unknown variable referenced + * @retval PEP_PATH_SYNTAX_ERROR invalid syntax in argument + * @retval PEP_OUT_OF_MEMORY out of memory + * + */ +static PEP_STATUS _expand_variables(char **out, + const char *string_with_variables) +{ + PEP_STATUS res = PEP_STATUS_OK; + size_t in_length = strlen(string_with_variables); + const char *variable_name_beginning; /* This points within the input. */ + char *variable_name_copy = NULL /* we free on error. */; + size_t allocated_size +#ifdef NDEBUG + = 1024; +#else + = 1 /* Notice that 0 is incorrect: this grows by doubling. */; +#endif // #ifdef NDEBUG + int out_index = 0; /* The out index is also the used out size */ + const char *in = string_with_variables; + /* In the pEp engine we adopt the convention of "" behaving the same as + NULL. Notice that we never free this, so it is not a problem if this + string is not malloc-allocated. */ + if (in == NULL) + in = ""; + /* We free on error. */ + * out = NULL ; + + /* Recognise a variable according to POSIX syntax which, luckily for us, + only allows for letters, digits and underscores -- The first character + may not be a digit... */ +#define VALID_FIRST_CHARACTER_FOR_VARIABLE(c) \ + ( ((c) >= 'a' && (c) <= 'z') \ + || ((c) >= 'A' && (c) <= 'Z') \ + || ((c) == '_')) + /* ...But characters after the first may be. */ +#define VALID_NON_FIRST_CHARACTER_FOR_VARIABLE(c) \ + ( VALID_FIRST_CHARACTER_FOR_VARIABLE(c) \ + || ((c) >= '0' && (c) <= '9')) + + /* Append the char argument to the result string, automatically resizing it + if needed. */ +#define EMIT_CHAR(c) \ + do \ + { \ + if (out_index == allocated_size) { \ + allocated_size *= 2; \ + /*fprintf (stderr, "ALLOCATED SIZE: %i -> %i\n", (int) allocated_size / 2, (int) allocated_size);*/\ + * out = realloc (* out, allocated_size); \ + if (* out == NULL) \ + FATAL (PEP_OUT_OF_MEMORY, \ + "cannot grow buffer"); \ + } \ + (* out) [out_index] = (c); \ + out_index ++; \ + } \ + while (false) + + /* Append the string argument to the output string, automatically resizing + it as needed. */ +#define EMIT_STRING(s) \ + do { \ + const char *p; \ + for (p = (s); (* p) != '\0'; p ++) \ + EMIT_CHAR (* p); \ + } while (false) + + /* Emit the expansion of the environment variable whose name is delimited on + the left by variable_name_beginning and on the right by the character + coming right *before* in. Fail fatally if the variable is unbound. + The expansion is emitted by appending to the result string, automatically + resizing it as needed. */ +#define EMIT_CURRENT_VARIABLE \ + do { \ + const char *variable_past_end = in; \ + size_t variable_name_length \ + = variable_past_end - variable_name_beginning; \ + strcpy (variable_name_copy, variable_name_beginning); \ + variable_name_copy [variable_name_length] = '\0'; \ + const char *variable_value = getenv (variable_name_copy); \ + if (variable_value == NULL) \ + FATAL_NAME (PEP_UNBOUND_ENVIRONMENT_VARIABLE, \ + "unbound variable", variable_name_copy); \ + EMIT_STRING (variable_value); \ + } while (false) + +#define FATAL(code, message) \ + do { res = (code); goto failure; } while (false) +#define FATAL_NAME(code, message, name) \ + FATAL((code), (message)) + + /* We can allocate buffers, now that we have FATAL. */ + if ((variable_name_copy + = malloc (in_length + 1 /* a safe upper bound for a sub-string. */)) + == NULL) + FATAL (PEP_OUT_OF_MEMORY, "out of mmeory"); + if (((* out) = malloc (allocated_size)) == NULL) + FATAL (PEP_OUT_OF_MEMORY, "out of memory"); + + /* This logic implements a DFA. */ + enum _expand_variable_state s = _expand_variable_state_non_variable; + char c; + while (true) { + c = * in; + switch (s) { + case _expand_variable_state_non_variable: + if (c == '$') { + variable_name_beginning = in + 1; + s = _expand_variable_state_after_dollar; + } + else if (c == '\\') + s = _expand_variable_state_after_backslash; + else /* This includes c == '\0'. */ + EMIT_CHAR (c); + if (c == '\0') + goto success; + break; + + case _expand_variable_state_after_backslash: + if (c == '$' || c == '\\') { + EMIT_CHAR (c); + s = _expand_variable_state_non_variable; + } + else if (c == '\0') /* Just to give a nicer error message */ + FATAL (PEP_PATH_SYNTAX_ERROR, "trailing unescaped '\\'"); + else /* this would be correct even with '\0' */ + FATAL (PEP_PATH_SYNTAX_ERROR, "invalid escape"); + break; + + case _expand_variable_state_after_dollar: + if (VALID_FIRST_CHARACTER_FOR_VARIABLE (c)) + s = _expand_variable_state_in_variable; + else if (c == '\0') /* Just to give a nicer error message */ + FATAL (PEP_PATH_SYNTAX_ERROR,"trailing '$' character"); + else if (c == '\\') /* Just to give a nicer error message */ + FATAL (PEP_PATH_SYNTAX_ERROR, + "empty variable name followed by escape"); + else if (c == '$') /* Just to give a nicer error message */ + FATAL (PEP_PATH_SYNTAX_ERROR, "two consecutive '$' characters"); + else + FATAL (PEP_PATH_SYNTAX_ERROR, + "invalid variable first character after '$'"); + break; + + case _expand_variable_state_in_variable: + if (VALID_NON_FIRST_CHARACTER_FOR_VARIABLE (c)) + /* Do nothing */; + else if (c == '\\') { + EMIT_CURRENT_VARIABLE; + s = _expand_variable_state_after_backslash; + } + else { + /* This includes c == '\0'. */ + EMIT_CURRENT_VARIABLE; + EMIT_CHAR (c); + if (c == '\0') + goto success; + else + s = _expand_variable_state_non_variable; + } + break; + + default: + FATAL (PEP_STATEMACHINE_INVALID_STATE /* Slightly questionable: this + should be an assertion. */, + "impossible DFA state"); + } /* switch */ + + in ++; + } /* while */ + + success: + free(variable_name_copy); + return res; + + failure: + free(* out); + * out = NULL; + goto success; +#undef VALID_FIRST_CHARACTER_FOR_VARIABLE +#undef VALID_NON_FIRST_CHARACTER_FOR_VARIABLE +#undef EMIT_CHAR +#undef EMIT_STRING +#undef EMIT_CURRENT_VARIABLE +#undef FATAL +#undef FATAL_NAME +} + + +/* + * Internal path caching functionality + * ********************************************************************** + */ + +/* Several functions in this compilation unit return paths to files or + * directories, always returning pointers to the same internally managed memory + * at every call. + * + * The cache is filled at engine initialisation, using the value of environment + * variables at initialisation time: after that point no out-of-memory errors + * are possible, until reset. + * + * In debugging mode the cache can be "reset", with every path recomputed on + * demand according to the current environment. + */ + +/* For each path we define: + - a static char * variable pointing to the cached value; + - a prototype for a static function returning a malloc-allocated copy of + the value, unexapanded, not using the cache (to be defined below by hand); + - a public API function returning a pointer to cached memory. */ +#define DEFINE_CACHED_PATH(name) \ + /* A static variable holding the cached path, or NULL. */ \ + static char *_ ## name ## _cache = NULL; \ + \ + /* A prototype for the hand-written function returning the \ + computed value for the path, without using the cache and \ + without expanding variables. */ \ + static char *_ ## name(void); \ + \ + /* The public version of the function, using the cache. */ \ + DYNAMIC_API const char *name(void) \ + { \ + if (_ ## name ## _cache == NULL) { \ + /* It is unusual and slightly bizarre than a path is \ + accessed before initialisation; however it can happen \ + in the engine test suite. */ \ + fprintf (stderr, \ + "WARNING: accessing %s before its cache is set:" \ + " this should not happen in production.\n", \ + #name); \ + reset_path_cache(); \ + } \ + assert (_ ## name ## _cache != NULL); \ + return _ ## name ## _cache; \ + } + +/* Define cached paths using the functionality above: */ +DEFINE_CACHED_PATH (per_user_relative_directory) +DEFINE_CACHED_PATH (per_user_directory) +DEFINE_CACHED_PATH (per_machine_directory) +#ifdef ANDROID + DEFINE_CACHED_PATH (android_system_db) +#endif +DEFINE_CACHED_PATH (unix_system_db) +DEFINE_CACHED_PATH (unix_local_db) + +/* Free every cache variable and re-initialise it to NULL: this + re-initialisation is important when this function is used here, + internally, as part of cleanup on errors. */ +DYNAMIC_API void clear_path_cache (void) +{ +#define UNSET(name) \ + do { \ + free((void *) _ ## name ## _cache); \ + (_ ## name ## _cache) = NULL; \ + } while (false) + + UNSET (per_user_relative_directory); + UNSET (per_user_directory); + UNSET (per_machine_directory); +#ifdef ANDROID + UNSET (android_system_db); +#endif + UNSET (unix_system_db); + UNSET (unix_local_db); + +#undef UNSET +} + +DYNAMIC_API PEP_STATUS reset_path_cache(void) +{ + PEP_STATUS res = PEP_STATUS_OK; + +#define SET_OR_FAIL(name) \ + do { \ + unexpanded_path = (_ ## name)(); \ + if (unexpanded_path == NULL) { \ + res = PEP_OUT_OF_MEMORY; \ + goto free_everything_and_fail; \ + } \ + res = _expand_variables(& _ ## name ## _cache, unexpanded_path); \ + if (res != PEP_STATUS_OK) \ + goto free_everything_and_fail; \ + /* Clear unxpanded_path for the next call of SET_OR_FAIL. */ \ + free((void *) unexpanded_path); \ + unexpanded_path = NULL; \ + } while (false) + + /* Start by releasing memory, which is needed in case this is not the first + invocation. */ + clear_path_cache (); + + const char *unexpanded_path = NULL; + + SET_OR_FAIL (per_user_relative_directory); + SET_OR_FAIL (per_user_directory); + SET_OR_FAIL (per_machine_directory); +#ifdef ANDROID + SET_OR_FAIL (android_system_db); +#endif + SET_OR_FAIL (unix_system_db); + SET_OR_FAIL (unix_local_db); + + return res; + + free_everything_and_fail: + free((void *) unexpanded_path); + clear_path_cache (); + return res; + +#undef SET_OR_FAIL +} + + +/** + * @internal + * + * + * + * @brief TODO + * + */ +static char *_per_user_relative_directory(void) +{ + return _strdup_or_NULL(PER_USER_DIRECTORY); +} + /** * @internal * @@ -413,27 +792,10 @@ static void _move(const char *o, const char *ext, const char *n) * * @brief TODO * - * @param[in] reset int - * */ -static const char *_per_user_directory(int reset) -#else -static const char *_per_user_directory(void) -#endif +static char *_per_user_directory(void) { - static char *path = NULL; - -#ifdef NDEBUG - if (path) - return path; -#else - if (path && !reset) - return path; - else if (path) { - free(path); - path = NULL; - } -#endif + char *path = NULL; const char *home = NULL; #ifndef NDEBUG @@ -465,32 +827,9 @@ error: return NULL; } -#ifdef NDEBUG -const char *unix_local_db(void) -#else -const char *unix_local_db(int reset) -#endif +char *_unix_local_db(void) { - static char *path = NULL; -#ifdef NDEBUG - if (path) -#else - if (path && !reset) -#endif - return path; - - const char* pathret = NULL; -#ifndef NDEBUG - pathret = _per_user_directory(reset); -#else - pathret = _per_user_directory(); -#endif - - if (!pathret) - return NULL; - - path = strdup(pathret); - assert(path); + char* path = (char *) _per_user_directory() /* This memory is not shared. */; if (!path) return NULL; @@ -602,26 +941,15 @@ the_end: return path; } -DYNAMIC_API const char *per_user_directory(void) { -#ifdef NDEBUG - return _per_user_directory(); -#else - return _per_user_directory(false); -#endif -} - -DYNAMIC_API const char *per_machine_directory(void) -{ - return PER_MACHINE_DIRECTORY; +static char *_per_machine_directory(void) { + return _strdup_or_NULL(PER_MACHINE_DIRECTORY); } -const char *unix_system_db(void) +char *_unix_system_db(void) { - static char *path = NULL; - if (path) - return path; + char *path = NULL; - path = strdup(per_machine_directory()); + path = _per_machine_directory() /* Use this fresh copy. */; assert(path); if (!path) return NULL; diff --git a/src/platform_unix.h b/src/platform_unix.h index a6932a52..85080a18 100644 --- a/src/platform_unix.h +++ b/src/platform_unix.h @@ -57,24 +57,24 @@ typedef unsigned char uuid_t[16]; extern "C" { #endif -#ifdef NDEBUG -const char *unix_local_db(void); -#else /** * * * @brief TODO - * - * @param[in] reset int + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. * */ -const char *unix_local_db(int reset); -#endif +const char *unix_local_db(void); + /** * * * @brief TODO - * + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. * */ const char *unix_system_db(void); @@ -91,6 +91,14 @@ char *stpcpy(char *, const char *); // Only the lowest 31 bits are filled randomly. //long int random(void); +/* + * + * + * @brief TODO + * The returned pointed refers memory managed by + * the engine, which will remain valid until + * the next call to reset_path_cache. + */ const char *android_system_db(void); #define SYSTEM_DB android_system_db() diff --git a/src/platform_windows.cpp b/src/platform_windows.cpp index 34441897..3db377db 100644 --- a/src/platform_windows.cpp +++ b/src/platform_windows.cpp @@ -22,6 +22,8 @@ #include #include +#include "pEpEngine.h" // just for PEP_STATUS + #define LOCAL_DB_FILENAME "management.db" #define SYSTEM_DB_FILENAME "system.db" #define KEYS_DB "keys.db" @@ -458,4 +460,16 @@ void log_output_debug(const char *title, OutputDebugStringA(str); } +DYNAMIC_API PEP_STATUS reset_path_cache(void) +{ + /* Do nothing and return success. This only exists for API compatibility + with the Unix version. */ + return PEP_STATUS_OK; +} + +DYNAMIC_API void clear_path_cache (void) +{ + /* Like reset_path_cache, do nothing. */ +} + } // "C" diff --git a/src/trans_auto.c b/src/trans_auto.c index ae371ca9..a039a95c 100644 --- a/src/trans_auto.c +++ b/src/trans_auto.c @@ -6,14 +6,46 @@ #include "trans_auto.h" -PEP_STATUS auto_sendto(PEP_SESSION session, const message *msg) +PEP_STATUS auto_configure(PEP_transport_t *transport, + transport_config_t *config, PEP_transport_status_code *tsc) { return PEP_STATUS_OK; } -PEP_STATUS auto_readnext(PEP_SESSION session, message **msg, PEP_transport_t **via) +PEP_STATUS auto_startup(PEP_transport_t *transport, + PEP_transport_status_code *tsc) { return PEP_STATUS_OK; } + +PEP_STATUS auto_shutdown(PEP_transport_t *transport, + PEP_transport_status_code *tsc) +{ + + return PEP_STATUS_OK; +} + +PEP_STATUS auto_sendto(PEP_SESSION session, message *msg, + PEP_transport_status_code *tsc) +{ + + return PEP_STATUS_OK; +} + +PEP_STATUS auto_recvnext(PEP_SESSION session, message **msg, + PEP_transport_status_code *tsc) +{ + + return PEP_STATUS_OK; +} + +PEP_STATUS auto_notify(signal_statuschange_t status_change, + signal_sendto_result_t sendto_result, + signal_incoming_message_t incoming, callback_execution cbe) +{ + + return PEP_STATUS_OK; +} + diff --git a/src/trans_auto.h b/src/trans_auto.h index 1cd85e3d..809e85c6 100644 --- a/src/trans_auto.h +++ b/src/trans_auto.h @@ -9,17 +9,23 @@ #include "transport.h" -PEP_STATUS auto_sendto(PEP_SESSION session, const message *msg); -/** - * - * - * @brief TODO - * - * @param[in] session PEP_SESSION - * @param[out] msg message** - * @param[out] via PEP_transport_t** - * - */ -PEP_STATUS auto_readnext(PEP_SESSION session, message **msg, PEP_transport_t **via); +PEP_STATUS auto_configure(PEP_transport_t *transport, + transport_config_t *config, PEP_transport_status_code *tsc); + +PEP_STATUS auto_startup(PEP_transport_t *transport, + PEP_transport_status_code *tsc); + +PEP_STATUS auto_shutdown(PEP_transport_t *transport, + PEP_transport_status_code *tsc); + +PEP_STATUS auto_sendto(PEP_SESSION session, message *msg, + PEP_transport_status_code *tsc); + +PEP_STATUS auto_recvnext(PEP_SESSION session, message **msg, + PEP_transport_status_code *tsc); + +PEP_STATUS auto_notify(signal_statuschange_t status_change, + signal_sendto_result_t sendto_result, + signal_incoming_message_t incoming, callback_execution cbe); #endif diff --git a/src/transport.c b/src/transport.c index 0e0bdbab..1d9a8d9b 100644 --- a/src/transport.c +++ b/src/transport.c @@ -22,8 +22,16 @@ PEP_STATUS init_transport_system(PEP_SESSION session, bool in_first) memset(transports, 0, sizeof(PEP_transport_t) * PEP_trans__count); transports[PEP_trans_auto].id = PEP_trans_auto; + transports[PEP_trans_auto].uri_scheme = ""; + + transports[PEP_trans_auto].configure = auto_configure; + transports[PEP_trans_auto].startup = auto_startup; + transports[PEP_trans_auto].shutdown = auto_shutdown; + transports[PEP_trans_auto].sendto = auto_sendto; - transports[PEP_trans_auto].readnext = auto_readnext; + transports[PEP_trans_auto].recvnext = auto_recvnext; + + transports[PEP_trans_auto].notify = auto_notify; } return PEP_STATUS_OK; diff --git a/src/transport.h b/src/transport.h index 062fb52e..da7419d5 100644 --- a/src/transport.h +++ b/src/transport.h @@ -8,32 +8,90 @@ #define TRANSPORT_H #include "pEpEngine.h" -#include "message.h" +#include "message_api.h" +#include #ifdef __cplusplus extern "C" { #endif /** - * @enum PEP_transports + * @enum PEP_transport_id * * @brief TODO * */ -typedef enum _PEP_transports { +typedef enum _PEP_transport_id { // auto transport chooses transport per message automatically PEP_trans_auto = 0, -// PEP_trans_email, -// PEP_trans_whatsapp, +// PEP_trans_Email = 0x01, +// PEP_trans_RCE = 0x02, +// PEP_trans_PDL = 0x03, +// PEP_trans_SCTP = 0x04, - PEP_trans__count -} PEP_transports; + PEP_trans__count, + PEP_trans_CC = 0xfe +} PEP_transport_id; + +typedef struct _transport_config { + // set size field when initializing + size_t size; + + // expand here + // in C++ this must be POD +} transport_config_t; + +// transports are delivering the transport status code +// this is defined here: +// https://dev.pep.foundation/Engine/TransportStatusCode typedef struct _PEP_transport_t PEP_transport_t; -typedef PEP_STATUS (*sendto_t)(PEP_SESSION session, const message *msg); -typedef PEP_STATUS (*readnext_t)(PEP_SESSION session, message **msg, - PEP_transport_t **via); +// functions offered by transport + +typedef PEP_STATUS (*configure_transport_t)(PEP_transport_t *transport, + transport_config_t *config, PEP_transport_status_code *tsc); + +typedef PEP_STATUS (*startup_transport_t)(PEP_transport_t *transport, + PEP_transport_status_code *tsc); + +typedef PEP_STATUS (*shutdown_transport_t)(PEP_transport_t *transport, + PEP_transport_status_code *tsc); + +typedef PEP_STATUS (*sendto_t)(PEP_SESSION session, message *msg, + PEP_transport_status_code *tsc); + +typedef PEP_STATUS (*recvnext_t)(PEP_SESSION session, message **msg, + PEP_transport_status_code *tsc); + +// callbacks + +typedef PEP_STATUS (*signal_statuschange_t)(PEP_transport_id id, + PEP_transport_status_code tsc); + +typedef PEP_STATUS (*signal_sendto_result_t)(PEP_transport_id id, char *message_id, + char *address, PEP_rating rating, PEP_transport_status_code tsc); + +typedef PEP_STATUS (*signal_incoming_message_t)(PEP_transport_id id, + PEP_transport_status_code tsc); + +// call this to receive signals + +typedef enum _callback_execution { + PEP_cbe_polling = 0, // execute callbacks immediately only + PEP_cbe_async, // execute callbacks multiple times later on any + // thread; call with PEP_cbe_polling to disable + + // the last one is for the transport system only + // do not implement it in transports + PEP_cbe_blocking = 255 +} callback_execution; + +// provide NULL for callbacks to avoid being called + +typedef PEP_STATUS (*notify_transport_t)(signal_statuschange_t status_change, + signal_sendto_result_t sendto_result, + signal_incoming_message_t incoming, callback_execution cbe); /** * @struct _PEP_transport_t @@ -42,13 +100,27 @@ typedef PEP_STATUS (*readnext_t)(PEP_SESSION session, message **msg, * */ struct _PEP_transport_t { - uint8_t id; // transport ID - sendto_t sendto; // sendto function - readnext_t readnext; // readnext function - bool long_message_supported; // flag if this transport supports - // long messages - bool formatted_message_supported; // flag if this transport supports - // formatted messages + PEP_transport_id id; // transport ID + const char *uri_scheme; // URI scheme this transport is + // covering + + // functions offered by transport + + configure_transport_t configure; + startup_transport_t startup; + shutdown_transport_t shutdown; + + sendto_t sendto; + recvnext_t recvnext; + + notify_transport_t notify; + + bool is_online_transport; + + bool shortmsg_supported; + bool longmsg_supported; + bool longmsg_formatted_supported; + PEP_text_format native_text_format; // native format of the transport }; diff --git a/test/README.md b/test/README.md index 553bc823..33b88c46 100644 --- a/test/README.md +++ b/test/README.md @@ -80,40 +80,61 @@ Ubuntu](https://www.eriksmistad.no/getting-started-with-google-test-on-ubuntu/)) 1. Get the source, Fred. (Luke is tired of the source, I hear.) ``` - git clone https://github.com/google/googletest.git + mkdir -p ~/pep-src/googletest + git clone https://github.com/google/googletest.git ~/pep-src/googletest ``` - 2. Switch into the source directory and find the directory - containing the `src` and `include` directories. Mark this directory - for later. (For me, this is `./googletest/googletest`) - - 3. Edit `CMakeLists.txt` here to contain the following line at the top: + 2. Switch into the source directory and find the subdirectory of + `googletest` (the source distribution of googletest also contains a + `googlemock` library which is not useful to us here) + containing the `src` and `include` directories. Remember this + directory's path. For me it is `~/pep-src/googletest/googletest` . + + 3. Edit the file `CMakeLists.txt` in that directory, adding the + following line at the top: ``` set (CMAKE_CXX_STANDARD 11) ``` (If you don't, it won't compile, and I will shake my fist at you.) - 4. Execute, in this directory: + 4. Go to the googletest source directory (it should be the parent + directory of the directory containing the file you edited) and build + the library. + ``` + cd ~/pep-src/googletest cmake CMakeLists.txt make ``` 5. In the lib directory of your current directory are located the - library files you'll use (`lib/*.a`). Copy or symlink them to the library + library files you'll use (`lib/*.a`). You may leave them there without + installing them, if you accept running a more complex command line than + ``` + make test + ``` + later. If you want to install the library, read on. + + Copy or symlink them to the library location of your choice (make sure this is a directory that can be seen during the test build process - i.e. one that's in one of the library paths used in building. Mine are located in `$HOME/lib`. - - 6. See `Makefile` and `local.conf` under "Building the test suite" below - + See `Makefile` and `local.conf` under "Building the test suite" below - In this scenario, I set `GTEST_SRC_DIR` as `/googletest/googletest` (i.e. the absolute path of where the `src` and `include` directories were - above - for me, `/Users/krista/googletest/googletest`). + above - for example, `/Users/krista/googletest/googletest`). ### Installing `gtest-parallel` Pick a source directory and put your `gtest-parallel` source there -(e.g. via `git clone https://github.com/google/gtest-parallel.git`). +(e.g. via `git clone https://github.com/google/gtest-parallel.git`): + +``` +mkdir -p ~/pep-src/gtest-parallel +git clone https://github.com/google/gtest-parallel.git ~/pep-src/gtest-parallel +``` + +This library is written in Python and does not require any actual build. We'll deal more with this when preparing to compile the test suite. @@ -133,13 +154,43 @@ are: * `GTEST_INC_DIR`: This is where the include files for googletest are located (defaults to `$(GTEST_SRC_DIR)/include`) - * `GTEST_PL`: This is the full path to the *python file* for `gtest_parallel` + * `GTEST_PL`: This is the full path to the *python file* for `gtest_parallel.py` (default presumes you cloned it under `src` in your home directory, i.e. it is `$(HOME)/src/gtest-parallel/gtest_parallel.py`) +The sample `local.conf` included in `../doc/build-debian.md` contains correct +definitions for these variables, assuming the user installed from sources under +`~/pep-src` as described here. + ### Building -Presuming the above works, then from the top test directory, simply run make. +Presuming the above works and you installed every library, then from the top test +directory, simply run ``make``. + +Libraries which have not been installed into the `lib` subdirectory of some +standard prefix will not be found automatically. But this problem is easy to +circumbent by setting environment libraries (on system using the GNU +linked-loader `LD_LIBRARY_PATH` for dynamic libraries, `LIBRARY_PATH` for static +libraries). + +For example, this should be sufficient to run the test suite under a debian +system following the instruction at `../doc/build-debian.md`: +``` +cd ~/pep-src/pep-engine +LIBRARY_PATH=$LIBRARY_PATH:$HOME/pep-src/googletest/lib LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/pep-src/pep-engine/src:$HOME/pep-src/sequoia/target/release:$HOME/pep-src/libetpan/build/lib make test +``` + +Such variable definitions may be prepended to the command lines below for running +individual tests or the entire test suite. + +In case of test failures remember to build the database, on which the test suite +depends, and to copy it into the system directory: +``` +make dbinstall +``` +The database does not need to be rebuilt and reinstalled for every test suite +run: running the `dbinstall` target once suffices. + ## Running the test suite diff --git a/test/src/ElevatedAttachmentsTest.cc b/test/src/ElevatedAttachmentsTest.cc index 9b6812f6..aeb4a768 100644 --- a/test/src/ElevatedAttachmentsTest.cc +++ b/test/src/ElevatedAttachmentsTest.cc @@ -226,6 +226,8 @@ TEST_F(ElevatedAttachmentsTest, check_encrypt_decrypt_message) { msg->shortmsg = strdup("Yo Bob!"); msg->longmsg = strdup("Look at my hot new sender fpr field!"); + // Volker: This is a sloppy way to test - it got processed as a real distribution message because data has meaning + // and happily exposed a bug in your generation code, but... well, you know better :) const char *distribution = "simulation of distribution data"; msg->attachments = new_bloblist(strdup(distribution), strlen(distribution) + 1, "application/pEp.distribution", "distribution.pEp"); diff --git a/test/src/Engine.cc b/test/src/Engine.cc index 45c8172f..1911597c 100644 --- a/test/src/Engine.cc +++ b/test/src/Engine.cc @@ -19,6 +19,7 @@ #include "TestUtilities.h" #include "Engine.h" #include "pEpTestStatic.h" +#include "pEpEngine_internal.h" #include #include "TestConstants.h" @@ -91,16 +92,25 @@ void Engine::start() { if (success != 0) throw std::runtime_error("SETUP: Cannot set engine_home for init."); - unix_local_db(true); - - PEP_STATUS status = init(&session, cached_messageToSend, cached_inject_sync_event, cached_ensure_passphrase); + PEP_STATUS status; + status = reset_path_cache(); + assert(status == PEP_STATUS_OK); + + status = init(&session, cached_messageToSend, cached_inject_sync_event, cached_ensure_passphrase); assert(status == PEP_STATUS_OK); assert(session); } void Engine::copy_conf_file_to_test_dir(const char* dest_path, const char* conf_orig_path, const char* conf_dest_name) { - string conf_dest_path = string(dest_path) + "/.pEp/"; - + string conf_dest_path + = (string(dest_path) + + string("/") + + string(per_user_relative_directory ()) + + string("/")); +fprintf (stderr, "COPYING %s from %s to %s\n", + dest_path, + conf_orig_path, + conf_dest_path.c_str ()); struct stat pathinfo; if(stat(conf_dest_path.c_str(), &pathinfo) != 0) { @@ -149,5 +159,6 @@ void Engine::shut_down() { if (success != 0) throw std::runtime_error("RESTORE: Cannot reset home directory! Either set environment variable manually back to your home, or quit this session!"); - unix_local_db(true); + PEP_STATUS status = reset_path_cache(); + assert(status == PEP_STATUS_OK); } diff --git a/test/src/Engine619Test.cc b/test/src/Engine619Test.cc new file mode 100644 index 00000000..779ee77a --- /dev/null +++ b/test/src/Engine619Test.cc @@ -0,0 +1,467 @@ +#include +#include +#include + +#include "internal_format.h" + +#include "TestUtilities.h" +#include "TestConstants.h" +#include "Engine.h" + +#include + + +namespace { + + //The fixture for Engine619Test + class Engine619Test : public ::testing::Test { + public: + Engine* engine; + PEP_SESSION session; + + protected: + // You can remove any or all of the following functions if its body + // is empty. + Engine619Test() { + // You can do set-up work for each test here. + test_suite_name = ::testing::UnitTest::GetInstance()->current_test_info()->GTEST_SUITE_SYM(); + test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + test_path = get_main_test_home_dir() + "/" + test_suite_name + "/" + test_name; + std::cout << "QQQQ " << get_main_test_home_dir() << "," << test_suite_name << "," << test_name << "\n"; + } + + ~Engine619Test() override { + // You can do clean-up work that doesn't throw exceptions here. + } + + // If the constructor and destructor are not enough for setting up + // and cleaning up each test, you can define the following methods: + + void SetUp() override { + // Code here will be called immediately after the constructor (right + // before each test). + + // Leave this empty if there are no files to copy to the home directory path + std::vector> init_files = std::vector>(); + + // Get a new test Engine. + engine = new Engine(test_path); + ASSERT_NOTNULL(engine); + + // Ok, let's initialize test directories etc. + engine->prep(NULL, NULL, NULL, init_files); + + // Ok, try to start this bugger. + engine->start(); + ASSERT_NOTNULL(engine->session); + session = engine->session; + + // Engine is up. Keep on truckin' + } + + void TearDown() override { + // Code here will be called immediately after each test (right + // before the destructor). + engine->shut_down(); + delete engine; + engine = NULL; + session = NULL; + } + + private: + const char* test_suite_name; + const char* test_name; + string test_path; + // Objects declared here can be used by all tests in the Engine619Test suite. + + }; + +} // namespace + +// TEST_F(Engine619Test, check_internal_format) { +// const char *data = "simulated data"; +// size_t data_size = strlen(data) + 1; + +// char *code; +// size_t code_size; + +// // test PGP keys + +// PEP_STATUS status = encode_internal(data, data_size, "application/pgp-keys", &code, &code_size); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(code_size, data_size + 4); + +// ASSERT_EQ(code[0], 0); +// ASSERT_EQ(code[1], 'K'); +// ASSERT_EQ(code[2], 2); + +// ASSERT_STREQ(code + 4, data); + +// // decode + +// char *value; +// size_t size; +// char *mime_type; + +// status = decode_internal(code, code_size, &value, &size, &mime_type); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(size, data_size); +// ASSERT_STREQ(value, data); +// ASSERT_STREQ(mime_type, "application/pgp-keys"); + +// free(value); +// free(code); + +// // test Sync + +// status = encode_internal(data, data_size, "application/pEp.sync", &code, &code_size); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(code_size, data_size + 4); + +// ASSERT_EQ(code[0], 0); +// ASSERT_EQ(code[1], 'S'); +// ASSERT_EQ(code[2], 0); + +// ASSERT_STREQ(code + 4, data); + +// // decode + +// status = decode_internal(code, code_size, &value, &size, &mime_type); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(size, data_size); +// ASSERT_STREQ(value, data); +// ASSERT_STREQ(mime_type, "application/pEp.sync"); + +// free(value); +// free(code); + +// // test Distribution + +// status = encode_internal(data, data_size, "application/pEp.distribution", &code, &code_size); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(code_size, data_size + 4); + +// ASSERT_EQ(code[0], 0); +// ASSERT_EQ(code[1], 'D'); +// ASSERT_EQ(code[2], 0); + +// ASSERT_STREQ(code + 4, data); + +// // decode + +// status = decode_internal(code, code_size, &value, &size, &mime_type); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(size, data_size); +// ASSERT_STREQ(value, data); +// ASSERT_STREQ(mime_type, "application/pEp.distribution"); + +// free(value); +// free(code); + +// // test PGP signature + +// status = encode_internal(data, data_size, "application/pgp-signature", &code, &code_size); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(code_size, data_size + 4); + +// ASSERT_EQ(code[0], 0); +// ASSERT_EQ(code[1], 'A'); +// ASSERT_EQ(code[2], 2); + +// ASSERT_STREQ(code + 4, data); + +// // decode + +// status = decode_internal(code, code_size, &value, &size, &mime_type); +// ASSERT_EQ(status, PEP_STATUS_OK); + +// ASSERT_EQ(size, data_size); +// ASSERT_STREQ(value, data); +// ASSERT_STREQ(mime_type, "application/pgp-signature"); + +// free(value); +// free(code); +// } + +// TEST_F(Engine619Test, check_encrypt_decrypt_message) { +// // a message from me, Alice, to Bob + +// const char* alice_fpr = "4ABE3AAF59AC32CFE4F86500A9411D176FF00E97"; +// const char* bob_fpr = "BFCDB7F301DEEEBBF947F29659BFF488C9C2EE39"; +// PEP_STATUS status = read_file_and_import_key(session, +// "test_keys/pub/pep-test-alice-0x6FF00E97_pub.asc"); +// ASSERT_EQ(status , PEP_KEY_IMPORTED); +// status = set_up_ident_from_scratch(session, +// "test_keys/priv/pep-test-alice-0x6FF00E97_priv.asc", +// "pep.test.alice@pep-project.org", alice_fpr, +// PEP_OWN_USERID, "Alice in Wonderland", NULL, true +// ); +// ASSERT_OK; +// ASSERT_TRUE(slurp_and_import_key(session, "test_keys/pub/pep-test-bob-0xC9C2EE39_pub.asc")); + +// message* msg = new_message(PEP_dir_outgoing); +// pEp_identity* alice = new_identity("pep.test.alice@pep-project.org", NULL, PEP_OWN_USERID, NULL); +// pEp_identity* bob = new_identity("pep.test.bob@pep-project.org", NULL, "Bob", NULL); +// status = myself(session, alice); +// ASSERT_OK; +// status = update_identity(session, bob); +// ASSERT_OK; +// bob->fpr = strdup(bob_fpr); +// status = set_identity(session, bob); +// ASSERT_OK; +// status = update_identity(session, bob); +// ASSERT_OK; +// status = set_as_pEp_user(session, bob); +// ASSERT_OK; + +// msg->to = new_identity_list(bob); +// msg->from = alice; +// msg->shortmsg = strdup("Yo Bob!"); +// msg->longmsg = strdup("Look at my hot new sender fpr field!"); + +// // Volker: This is a sloppy way to test - it got processed as a real distribution message because data has meaning +// // and happily exposed a bug in your generation code, but... well, you know better :) +// const char *distribution = "simulation of distribution data"; +// msg->attachments = new_bloblist(strdup(distribution), strlen(distribution) +// + 1, "application/pEp.distribution", "distribution.pEp"); + +// // encrypt this message inline + +// message* enc_msg = NULL; +// status = encrypt_message(session, msg, NULL, &enc_msg, PEP_enc_inline, 0); +// ASSERT_OK; + +// // .shortmsg will stay unencrypted +// ASSERT_STREQ(msg->shortmsg, enc_msg->shortmsg); + +// // .longmsg will go encrypted +// ASSERT_TRUE(is_PGP_message_text(enc_msg->longmsg)); + +// ASSERT_TRUE(enc_msg->attachments); +// ASSERT_TRUE(enc_msg->attachments->value); + +// bloblist_t *ad = enc_msg->attachments; + +// // distribution message is encrypted +// ASSERT_TRUE(is_PGP_message_text(ad->value)); +// ASSERT_STREQ(ad->mime_type, "application/octet-stream"); +// ASSERT_STREQ(ad->filename, "distribution.pEp.pgp"); + +// // next attachment +// ASSERT_TRUE(ad->next); +// ad = ad->next; + +// // attached key is encrypted +// ASSERT_TRUE(is_PGP_message_text(ad->value)); +// ASSERT_STREQ(ad->mime_type, "application/octet-stream"); +// // ASSERT_STREQ(ad->filename, "file://pEpkey.asc.pgp"); +// // As of ENGINE-633: +// ASSERT_STREQ(ad->filename, "file://sender_key.asc.pgp"); +// // decrypt this message + +// message *dec_msg = NULL; +// stringlist_t *keylist = NULL; +// PEP_rating rating; +// PEP_decrypt_flags_t flags = 0; + +// status = decrypt_message(session, enc_msg, &dec_msg, &keylist, &rating, &flags); +// ASSERT_EQ(status, PEP_STATUS_OK); +// ASSERT_STREQ(dec_msg->shortmsg, enc_msg->shortmsg); +// ASSERT_STREQ(msg->longmsg, dec_msg->longmsg); + +// // check attachments +// ASSERT_TRUE(dec_msg->attachments); +// ASSERT_TRUE(dec_msg->attachments->value); +// bloblist_t *as = dec_msg->attachments; +// bloblist_t *bl = msg->attachments; + +// ASSERT_STREQ(as->filename, "file://distribution.pEp"); +// // the MIME will be derived from filename +// ASSERT_STREQ(as->mime_type, "application/pEp.distribution"); + +// free_message(msg); +// free_message(enc_msg); +// free_message(dec_msg); +// } + +// TEST_F(Engine619Test, check_encrypt_decrypt_message_elevated) { +// // a message from me, Alice, to Bob + +// const char* alice_fpr = "4ABE3AAF59AC32CFE4F86500A9411D176FF00E97"; +// const char* bob_fpr = "BFCDB7F301DEEEBBF947F29659BFF488C9C2EE39"; +// PEP_STATUS status = read_file_and_import_key(session, +// "test_keys/pub/pep-test-alice-0x6FF00E97_pub.asc"); +// ASSERT_EQ(status , PEP_KEY_IMPORTED); +// status = set_up_ident_from_scratch(session, +// "test_keys/priv/pep-test-alice-0x6FF00E97_priv.asc", +// "pep.test.alice@pep-project.org", alice_fpr, +// PEP_OWN_USERID, "Alice in Wonderland", NULL, true +// ); +// ASSERT_OK; +// ASSERT_TRUE(slurp_and_import_key(session, "test_keys/pub/pep-test-bob-0xC9C2EE39_pub.asc")); + +// message* msg = new_message(PEP_dir_outgoing); +// pEp_identity* alice = new_identity("pep.test.alice@pep-project.org", NULL, PEP_OWN_USERID, NULL); +// pEp_identity* bob = new_identity("pep.test.bob@pep-project.org", NULL, "Bob", NULL); +// status = myself(session, alice); +// ASSERT_OK; +// status = update_identity(session, bob); +// ASSERT_OK; +// bob->fpr = strdup(bob_fpr); +// status = set_identity(session, bob); +// ASSERT_OK; +// status = update_identity(session, bob); +// ASSERT_OK; +// status = set_as_pEp_user(session, bob); +// ASSERT_OK; + +// msg->to = new_identity_list(bob); +// msg->from = alice; +// msg->shortmsg = strdup("Yo Bob!"); +// msg->longmsg = strdup("Look at my hot new sender fpr field!"); + +// const char *distribution = "simulation of distribution data"; +// msg->attachments = new_bloblist(strdup(distribution), strlen(distribution) +// + 1, "application/pEp.distribution", "distribution.pEp"); + +// // encrypt this message inline + +// message* enc_msg = NULL; +// status = encrypt_message(session, msg, NULL, &enc_msg, PEP_enc_inline_EA, 0); +// ASSERT_OK; + +// // .longmsg will go encrypted +// ASSERT_TRUE(is_PGP_message_text(enc_msg->longmsg)); + +// ASSERT_TRUE(enc_msg->attachments); +// ASSERT_TRUE(enc_msg->attachments->value); + +// bloblist_t *ad = enc_msg->attachments; + +// // distribution message is encrypted +// ASSERT_TRUE(is_PGP_message_text(ad->value)); +// ASSERT_STREQ(ad->mime_type, "application/octet-stream"); +// ASSERT_STREQ(ad->filename, "distribution.pEp.pgp"); + +// // next attachment +// ASSERT_TRUE(ad->next); +// ad = ad->next; + +// // attached key is encrypted +// ASSERT_TRUE(is_PGP_message_text(ad->value)); +// ASSERT_STREQ(ad->mime_type, "application/octet-stream"); +// // ASSERT_STREQ(ad->filename, "file://pEpkey.asc.pgp"); +// // As of ENGINE-633: +// ASSERT_STREQ(ad->filename, "file://sender_key.asc.pgp"); +// // decrypt this message + +// char *ct = strdup(ad->value); + +// { +// // test if this is an elevated attachment + +// char *pt; +// size_t pt_size; +// stringlist_t *keylist; + +// // decrypt this part + +// status = decrypt_and_verify(session, ct, strlen(ct) + 1, NULL, 0, &pt, &pt_size, &keylist, NULL); +// ASSERT_EQ(status, PEP_DECRYPTED_AND_VERIFIED); + +// // decode internal message format + +// char *dt; +// size_t dt_size; +// char *mime_type; +// status = decode_internal(pt, pt_size, &dt, &dt_size, &mime_type); +// ASSERT_EQ(status, PEP_STATUS_OK); +// ASSERT_TRUE(dt); +// ASSERT_STREQ(mime_type, "application/pgp-keys"); + +// free(pt); +// free(dt); +// free(mime_type); +// free_stringlist(keylist); +// } + +// // create artificial message for Key like a transport would do + +// message *art_msg = new_message(PEP_dir_incoming); +// art_msg->enc_format = PEP_enc_inline_EA; +// art_msg->from = identity_dup(enc_msg->to->ident); +// art_msg->to = new_identity_list(identity_dup(enc_msg->from)); +// art_msg->longmsg = ct; + +// // decrypt this message + +// message *dec_msg = NULL; +// stringlist_t *keylist = NULL; +// PEP_rating rating; +// PEP_decrypt_flags_t flags = 0; + +// status = decrypt_message(session, art_msg, &dec_msg, &keylist, &rating, &flags); +// ASSERT_EQ(status, PEP_STATUS_OK); +// ASSERT_TRUE(dec_msg); +// // today the engine is sucking keys in +// // ASSERT_STREQ(dec_msg->attachments->mime_type, "application/pgp-keys"); +// ASSERT_STREQ(dec_msg->shortmsg, "pEp"); + +// stringpair_list_t *of; +// bool pEp_auto_consume_found = false; +// for (of = dec_msg->opt_fields; of && of->value; of = of->next) { +// if (strcasecmp(of->value->key, "pEp-auto-consume") == 0) { +// ASSERT_STREQ(of->value->value, "yes"); +// pEp_auto_consume_found = true; +// break; +// } +// } +// ASSERT_TRUE(pEp_auto_consume_found); + +// free_message(msg); +// free_message(enc_msg); +// free_message(dec_msg); +// free_message(art_msg); +// } + +TEST_F(Engine619Test, decrypt_message_with_private_key) { + PEP_STATUS status = PEP_STATUS_OK; + message *message_src + = slurp_message_file_into_struct("test_mails/ExtraKeyPrivateKeyAttached.eml"); + ASSERT_NOTNULL (message_src); + + message *message_dst; + stringlist_t* keys = NULL; + PEP_rating rating; + PEP_decrypt_flags_t flags = 0; + + status = decrypt_message(session, message_src, & message_dst, + & keys, & rating, & flags); + /* It is normal and expected that decryption fails here: this message is + unencrypted. The point of this test is to arrive at a call to + pgp_import_keydata , which used to corrut the heap (ENGINE-619). */ + ASSERT_EQ (status, PEP_UNENCRYPTED); + + /* Check that the message content matches what we expect. */ + ASSERT_NOTNULL (message_src->shortmsg); + ASSERT_NOTNULL (message_src->longmsg); + ASSERT_NULL (message_src->longmsg_formatted); + + /* The message has exactly one attachment, the private key. */ + bloblist_t *attachments = message_src->attachments; + ASSERT_NOTNULL (attachments); + ASSERT_NULL (attachments->next); + + /* We can even print it. */ + std::cerr << "THE FIRST ATTACHMENT HAS SIZE " << attachments->size << "\n"; + std::cerr << "THE FIRST ATTACHMENT IS:\n"; + std::cerr << attachments->value; + std::cerr << "\n"; +} diff --git a/test/src/GroupEncryptionTest.cc b/test/src/GroupEncryptionTest.cc index ae4de663..91a10500 100644 --- a/test/src/GroupEncryptionTest.cc +++ b/test/src/GroupEncryptionTest.cc @@ -2358,7 +2358,7 @@ TEST_F(GroupEncryptionTest, check_protocol_group_create_different_own_identity_m const char* member_addrs[] = {member_1_address, member_2_address, member_3_address, member_4_address}; const char* member_fprs[] = {member_1_fpr, member_2_fpr, member_3_fpr, member_4_fpr}; - bool found[] = {false, false, false}; + bool found[] = {false, false, false, false}; int count = 0; for (member_list* curr_member = group1_info->members; diff --git a/test/src/MapAsn1Test.cc b/test/src/MapAsn1Test.cc index 90a61ee8..314a03bf 100644 --- a/test/src/MapAsn1Test.cc +++ b/test/src/MapAsn1Test.cc @@ -10,6 +10,7 @@ #include "pEpEngine.h" #include "pEp_internal.h" #include "map_asn1.h" +#include "message_codec.h" #include "TestUtilities.h" @@ -119,3 +120,84 @@ TEST_F(MapAsn1Test, check_map_asn1) { free_identity(ident1); free_identity(ident2); } + +TEST_F(MapAsn1Test, check_map_asn1_message) { + output_stream << "testing ASN1Message...\n"; + + message *msg = new_message(PEP_dir_outgoing); + msg->id = strdup("423"); + msg->shortmsg = strdup("hello, world"); + msg->longmsg = strdup("long message"); + msg->longmsg_formatted = strdup("

long message

"); + msg->attachments = new_bloblist(strdup("blob"), 5, "text/plain", "test.txt"); + bloblist_add(msg->attachments, strdup("bla"), 4, "application/octet-stream", "data.dat"); + msg->sent = new_timestamp(23); + msg->recv = new_timestamp(42); + msg->from = new_identity("alice@mail.com", "2342234223422342", "23", "Alice Miller"); + msg->from->comm_type = PEP_ct_pEp; + msg->from->lang[0] = 'd'; msg->from->lang[1] = 'e'; + msg->to = new_identity_list(new_identity("bob@mail.com", "4223422342234223", "42", "Bob Smith")); + identity_list_add(msg->to, new_identity("alice@mail.com", "2342234223422342", "23", "Alice Miller")); + msg->recv_by = new_identity("bob@mail.com", "4223422342234223", "42", "Bob Smith"); + msg->cc = new_identity_list(new_identity("bob@mail.com", "4223422342234223", "42", "Bob Smith")); + identity_list_add(msg->cc, new_identity("alice@mail.com", "2342234223422342", "23", "Alice Miller")); + msg->bcc = new_identity_list(new_identity("bob@mail.com", "4223422342234223", "42", "Bob Smith")); + identity_list_add(msg->bcc, new_identity("alice@mail.com", "2342234223422342", "23", "Alice Miller")); + msg->reply_to = new_identity_list(new_identity("bob@mail.com", "4223422342234223", "42", "Bob Smith")); + identity_list_add(msg->reply_to, new_identity("alice@mail.com", "2342234223422342", "23", "Alice Miller")); + msg->in_reply_to = new_stringlist("23234242"); + stringlist_add(msg->in_reply_to, "323234242"); + msg->references = new_stringlist("23234242"); + stringlist_add(msg->references , "323234242"); + msg->keywords = new_stringlist("something"); + stringlist_add(msg->keywords, "else"); + msg->comments = strdup("hello there"); + msg->opt_fields = new_stringpair_list(new_stringpair("key", "value")); + stringpair_list_add(msg->opt_fields, new_stringpair("otherkey", "othervalue")); + msg->_sender_fpr = strdup("2342234223422342"); + + ASN1Message_t *pm = ASN1Message_from_message(msg, NULL, false, 1024); + + char *data = NULL; + size_t data_size = 0; + PEP_STATUS status = encode_ASN1Message_message(pm, &data, &data_size); + ASSERT_EQ(status, PEP_STATUS_OK); + + ASN1Message_t *pm2 = NULL; + status = decode_ASN1Message_message(data, data_size, &pm2); + ASSERT_EQ(status, PEP_STATUS_OK); + + message *msg2 = ASN1Message_to_message(pm2, NULL, false, 1024); + + ASSERT_STREQ(msg2->id, "423"); + ASSERT_STREQ(msg2->shortmsg, "hello, world"); + ASSERT_STREQ(msg2->longmsg, "long message"); + ASSERT_STREQ(msg2->longmsg_formatted, "

long message

"); + ASSERT_STREQ(msg2->attachments->mime_type, "text/plain"); + ASSERT_EQ(msg2->attachments->next->value[0], 'b'); + ASSERT_NULL(msg2->attachments->next->next); + ASSERT_EQ(msg2->sent->tm_sec, 23); + ASSERT_EQ(msg2->recv->tm_sec, 42); + ASSERT_STREQ(msg2->from->user_id, "23"); + ASSERT_STREQ(msg2->to->ident->user_id, "42"); + ASSERT_STREQ(msg2->to->next->ident->user_id, "23"); + ASSERT_STREQ(msg2->recv_by->user_id, "42"); + ASSERT_STREQ(msg2->cc->next->ident->user_id, "23"); + ASSERT_STREQ(msg2->bcc->next->ident->user_id, "23"); + ASSERT_STREQ(msg2->reply_to->next->ident->user_id, "23"); + ASSERT_STREQ(msg2->in_reply_to->value, "23234242"); + ASSERT_STREQ(msg2->in_reply_to->next->value, "323234242"); + ASSERT_STREQ(msg2->references->next->value, "323234242"); + ASSERT_STREQ(msg2->keywords->next->value, "else"); + ASSERT_STREQ(msg2->comments, "hello there"); + ASSERT_STREQ(msg2->opt_fields->value->key, "key"); + ASSERT_STREQ(msg2->opt_fields->next->value->value, "othervalue"); + ASSERT_STREQ(msg2->_sender_fpr, "2342234223422342"); + + ASN_STRUCT_FREE(asn_DEF_ASN1Message, pm); + ASN_STRUCT_FREE(asn_DEF_ASN1Message, pm2); + free_message(msg); + free_message(msg2); + free(data); +} + diff --git a/test/src/TestUtilities.h b/test/src/TestUtilities.h index 83b4e023..b4c3f32f 100644 --- a/test/src/TestUtilities.h +++ b/test/src/TestUtilities.h @@ -11,6 +11,7 @@ #include "pEpEngine.h" #include "message_api.h" #include "mime.h" +#include "status_to_string.h" #include @@ -37,7 +38,14 @@ char* get_new_uuid(); */ #ifndef ASSERT_OK -#define ASSERT_OK ASSERT_EQ(status, PEP_STATUS_OK) +#define ASSERT_OK \ + do { \ + if (status != PEP_STATUS_OK) \ + std::cout << "status is " << status << " (" \ + << pEp_status_to_string(status) << ")" \ + << std::endl; \ + ASSERT_EQ(status, PEP_STATUS_OK); \ + } while (false) #endif #ifndef ASSERT_NOTNULL