├── .github └── workflows │ └── sync.yaml ├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── examples ├── libsam3 ├── sam3 │ ├── Makefile │ ├── README.md │ ├── dgramc.c │ ├── dgrams.c │ ├── keys.c │ ├── keys.cc │ ├── namelookup.c │ ├── samtest.c │ ├── streamc.c │ ├── streamcs.c │ ├── streams.c │ └── streamss.c └── sam3a │ ├── test00.c │ ├── test_sc.c │ └── test_ss.c ├── src ├── ext │ ├── tinytest.c │ ├── tinytest.h │ └── tinytest_macros.h ├── libsam3 │ ├── libsam3.c │ └── libsam3.h └── libsam3a │ ├── libsam3a.c │ └── libsam3a.h └── test ├── libsam3 └── test_b32.c └── test.c /.github/workflows/sync.yaml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow file to sync an external repository to this GitHub mirror. 2 | # This file was automatically generated by go-github-sync. 3 | # 4 | # The workflow does the following: 5 | # - Runs on a scheduled basis (and can also be triggered manually) 6 | # - Clones the GitHub mirror repository 7 | # - Fetches changes from the primary external repository 8 | # - Applies those changes to the mirror repository 9 | # - Pushes the updated content back to the GitHub mirror 10 | # 11 | # Authentication is handled by the GITHUB_TOKEN secret provided by GitHub Actions. 12 | 13 | jobs: 14 | sync: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Validate Github Actions Environment 18 | run: if [ "$GITHUB_ACTIONS" != "true" ]; then echo 'This script must be run in a GitHub Actions environment.'; exit 1; fi 19 | - name: Checkout GitHub Mirror 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | - name: Configure Git 24 | run: |- 25 | git config user.name 'GitHub Actions' 26 | git config user.email 'actions@github.com' 27 | - env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | name: Sync Primary Repository 30 | run: |- 31 | # Add the primary repository as a remote 32 | git remote add primary https://i2pgit.org/I2P_Developers/libsam3.git 33 | 34 | # Fetch the latest changes from the primary repository 35 | git fetch primary 36 | 37 | # Check if the primary branch exists in the primary repository 38 | if git ls-remote --heads primary master | grep -q master; then 39 | echo "Primary branch master found in primary repository" 40 | else 41 | echo "Error: Primary branch master not found in primary repository" 42 | exit 1 43 | fi 44 | 45 | # Check if we're already on the mirror branch 46 | if git rev-parse --verify --quiet master; then 47 | git checkout master 48 | else 49 | # Create the mirror branch if it doesn't exist 50 | git checkout -b master 51 | fi 52 | 53 | 54 | # Force-apply all changes from primary, overriding any conflicts 55 | echo "Performing force sync from primary/master to master" 56 | git reset --hard primary/master 57 | 58 | 59 | # Push changes back to the mirror repository 60 | git push origin master 61 | name: Sync Primary Repository to GitHub Mirror 62 | "on": 63 | push: {} 64 | schedule: 65 | - cron: 0 * * * * 66 | workflow_dispatch: {} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | libsam3-tests 3 | *.a 4 | examples/libsam3 5 | examples/sam3/dgrams 6 | examples/sam3/dgramc 7 | examples/sam3/keys 8 | examples/sam3/keysp 9 | examples/sam3namelookup 10 | examples/sam3/streamss 11 | examples/sam3/streamcs 12 | examples/sam3/log 13 | examples/sam3/err 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | script: make 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -Wall -g -O2 -std=gnu99 2 | 3 | SRCS := \ 4 | src/libsam3/libsam3.c \ 5 | src/libsam3a/libsam3a.c 6 | 7 | TESTS := \ 8 | src/ext/tinytest.c \ 9 | test/test.c \ 10 | test/libsam3/test_b32.c 11 | 12 | LIB_OBJS := ${SRCS:.c=.o} 13 | TEST_OBJS := ${TESTS:.c=.o} 14 | 15 | OBJS := ${LIB_OBJS} ${TEST_OBJS} 16 | 17 | LIB := libsam3.a 18 | 19 | all: build check 20 | 21 | check: libsam3-tests 22 | ./libsam3-tests 23 | 24 | build: ${LIB} 25 | 26 | ${LIB}: ${LIB_OBJS} 27 | ${AR} -sr ${LIB} ${LIB_OBJS} 28 | 29 | libsam3-tests: ${TEST_OBJS} ${LIB} 30 | ${CC} $^ -o $@ 31 | 32 | clean: 33 | rm -f libsam3-tests ${LIB} ${OBJS} examples/sam3/samtest 34 | 35 | # TODO: this does not work yet because I don't know how to do it. 36 | boost: 37 | gcc -Werror -Wall -Wextra -Wno-unused-parameter -std=c++11 -g -lboost_system -lboost_thread -lpthread examples/boost/boost.cpp -o boost 38 | 39 | %.o: %.c Makefile 40 | ${CC} ${CFLAGS} $(LDFLAGS) -c $< -o $@ 41 | 42 | fmt: 43 | find . -name '*.c' -exec clang-format -i {} \; 44 | find . -name '*.h' -exec clang-format -i {} \; 45 | 46 | info: 47 | @echo $(AR) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsam3 2 | 3 | [![Build Status](https://travis-ci.org/i2p/libsam3.svg?branch=master)](https://travis-ci.org/i2p/libsam3) 4 | 5 | A C library for the [SAM v3 API](https://geti2p.net/en/docs/api/samv3). 6 | 7 | ## Development Status 8 | 9 | Maintained by idk, PRs are accepted on [I2P gitlab](https://i2pgit.org/i2p-hackers/libsam3)/[I2P gitlab](http://git.idk.i2p/i2p-hackers/libsam3), and on github at the official mirror repository: [i2p/libsam3](https://github.com/i2p/libsam3). 10 | 11 | ## Usage 12 | 13 | Copy the two files from one of the following locations into your codebase: 14 | 15 | - `src/libsam3` - Synchronous implementation. 16 | - `src/libsam3a` - Asynchronous implementation. 17 | 18 | See `examples/` for how to use various parts of the API. 19 | 20 | ## Cross-Compiling for Windows from debian: 21 | 22 | Set your cross-compiler up: 23 | 24 | ``` sh 25 | export CC=x86_64-w64-mingw32-gcc 26 | export CFLAGS='-Wall -O2 ' 27 | export LDFLAGS='-lmingw32 -lws2_32 -lwsock32 -mwindows' 28 | ``` 29 | 30 | and run `make build`. Only libsam3 is available for Windows, libsam3a will be 31 | made available at a later date. 32 | ` 33 | 34 | ## Linker(Windows) 35 | 36 | When building for Windows remember to set the flags to link to the Winsock and Windows 37 | libraries. 38 | 39 | `-lmingw32 -lws2_32 -lwsock32 -mwindows` 40 | 41 | This may apply when cross-compiling or compiling from Windows with mingw. 42 | 43 | ## Cool Projects using libsam3 44 | 45 | Are you using libsam3 to provide an a cool I2P based feature to your project? Let us know about it(and how 46 | it uses libsam3) and we'll think about adding it here*! 47 | 48 | 1. [Retroshare](https://retroshare.cc) 49 | 50 | *Projects which are listed here must be actively maintained. Those which intentionally violate 51 | the law or the rights of a person or persons directly won't be considered. Neither will obvious 52 | trolling. The maintainer will make the final decision. 53 | -------------------------------------------------------------------------------- /examples/libsam3: -------------------------------------------------------------------------------- 1 | ../src/libsam3 -------------------------------------------------------------------------------- /examples/sam3/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -Wall -g -O2 -std=gnu99 2 | 3 | all: clean examples 4 | 5 | examples: example lookup dclient dserver sclient sserver ssserver ssclient 6 | 7 | example: 8 | ${CC} ${CFLAGS} samtest.c -o samtest ../libsam3/libsam3.o 9 | 10 | lookup: 11 | ${CC} ${CFLAGS} namelookup.c -o lookup ../libsam3/libsam3.o 12 | 13 | dclient: 14 | ${CC} ${CFLAGS} dgramc.c -o dgramc ../libsam3/libsam3.o 15 | 16 | dserver: 17 | ${CC} ${CFLAGS} dgrams.c -o dgrams ../libsam3/libsam3.o 18 | 19 | sclient: 20 | ${CC} ${CFLAGS} streamc.c -o streamc ../libsam3/libsam3.o 21 | 22 | sserver: 23 | ${CC} ${CFLAGS} streams.c -o streams ../libsam3/libsam3.o 24 | 25 | ssclient: 26 | ${CC} ${CFLAGS} streamcs.c -o streamcs ../libsam3/libsam3.o 27 | 28 | ssserver: 29 | ${CC} ${CFLAGS} streamss.c -o streamss ../libsam3/libsam3.o 30 | 31 | keysp: 32 | ${CXX} ${CFLAGS} keys.cc -o keysp ../libsam3/libsam3.o 33 | 34 | keys: 35 | ${CC} ${CFLAGS} keys.c -o keys ../libsam3/libsam3.o 36 | 37 | clean: 38 | rm -f samtest lookup dgramc dgrams streamc streams streams.key test-lookup keys keysp 39 | 40 | debug: 41 | sed -i 's|// libsam3_debug = 1;|libsam3_debug = 1;|g' *.c 42 | 43 | nodebug: 44 | sed -i 's|libsam3_debug = 1;|// libsam3_debug = 1;|g' *.c 45 | -------------------------------------------------------------------------------- /examples/sam3/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | These examples show various ways of using libsam3 to enable i2p in your 5 | application, and are also useful in other ways. If you implement an i2p 6 | application library in another language, making variants basic tools wouldn't be 7 | the worst way to make sure that it works. 8 | 9 | building 10 | -------- 11 | 12 | Once you have build the library in the root of this repository by running make 13 | all, you can build all these examples at once by running 14 | 15 | make 16 | 17 | in this directory. I think it makes things easier to experiment with quickly. 18 | 19 | namelookup 20 | ---------- 21 | 22 | Namelookup uses the SAM API to find the base64 destination of an readable "jump" 23 | or base32 i2p address. You can use it like this: 24 | 25 | ./lookup i2p-projekt.i2p 26 | 27 | -------------------------------------------------------------------------------- /examples/sam3/dgramc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | // comment the following if you don't want to stress UDP with 'big' datagram 35 | // seems that up to 32000 bytes can be used for localhost 36 | // note that we need 516+6+? bytes for header; lets reserve 1024 bytes for it 37 | #define BIG (32000 - 1024) 38 | 39 | #define KEYFILE "dgrams.key" 40 | 41 | int main(int argc, char *argv[]) { 42 | Sam3Session ses; 43 | char buf[1024]; 44 | char destkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + 1] = {0}; 45 | int sz; 46 | size_t sizeread; 47 | // 48 | libsam3_debug = 1; 49 | // 50 | if (argc < 2) { 51 | FILE *fl = fopen(KEYFILE, "rb"); 52 | // 53 | if (fl != NULL) { 54 | fprintf(stderr, "Reading key file...\n"); 55 | sizeread = fread(destkey, sizeof(char), sizeof(destkey), fl); 56 | fprintf(stderr, "Read %li bytes\n", sizeread); 57 | if (ferror(fl) != 0) { 58 | fprintf(stderr, "I/O Error\n"); 59 | return 1; 60 | } 61 | // Insure that the bytes read safely fits into destkey 62 | if (sizeread == sizeof(destkey)) { 63 | fprintf(stderr, "Error, key file is to large (> %li)\n", sizeread); 64 | fclose(fl); 65 | return 1; 66 | } 67 | fclose(fl); 68 | goto ok; 69 | } 70 | printf("usage: dgramc PUBKEY\n"); 71 | return 1; 72 | } else { 73 | if (strlen(argv[1]) > (SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE)) { 74 | fprintf(stderr, "FATAL: invalid key length (%li)!\n", strlen(argv[1])); 75 | return 1; 76 | } 77 | strcpy(destkey, argv[1]); 78 | } 79 | // 80 | ok: 81 | printf("creating session...\n"); 82 | /* create TRANSIENT session with temporary disposible destination */ 83 | if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 84 | SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_DGRAM, 4, 85 | NULL) < 0) { 86 | fprintf(stderr, "FATAL: can't create session\n"); 87 | return 1; 88 | } 89 | /* send datagram */ 90 | printf("sending test datagram...\n"); 91 | if (sam3DatagramSend(&ses, destkey, "test", 4) < 0) { 92 | fprintf(stderr, "ERROR: %s\n", ses.error); 93 | goto error; 94 | } 95 | /** receive reply */ 96 | if ((sz = sam3DatagramReceive(&ses, buf, sizeof(buf) - 1)) < 0) { 97 | fprintf(stderr, "ERROR: %s\n", ses.error); 98 | goto error; 99 | } 100 | /** null terminated string */ 101 | buf[sz] = 0; 102 | printf("received: [%s]\n", buf); 103 | // 104 | #ifdef BIG 105 | { 106 | char *big = calloc(BIG + 1024, sizeof(char)); 107 | /** generate random string */ 108 | sam3GenChannelName(big, BIG + 1023, BIG + 1023); 109 | printf("sending BIG datagram...\n"); 110 | if (sam3DatagramSend(&ses, destkey, big, BIG) < 0) { 111 | free(big); 112 | fprintf(stderr, "ERROR: %s\n", ses.error); 113 | goto error; 114 | } 115 | if ((sz = sam3DatagramReceive(&ses, big, BIG + 512)) < 0) { 116 | free(big); 117 | fprintf(stderr, "ERROR: %s\n", ses.error); 118 | goto error; 119 | } 120 | big[sz] = 0; 121 | printf("received (%d): [%s]\n", sz, big); 122 | free(big); 123 | } 124 | #endif 125 | // 126 | printf("sending quit datagram...\n"); 127 | if (sam3DatagramSend(&ses, destkey, "quit", 4) < 0) { 128 | fprintf(stderr, "ERROR: %s\n", ses.error); 129 | goto error; 130 | } 131 | if ((sz = sam3DatagramReceive(&ses, buf, sizeof(buf) - 1)) < 0) { 132 | fprintf(stderr, "ERROR: %s\n", ses.error); 133 | goto error; 134 | } 135 | buf[sz] = 0; 136 | printf("received: [%s]\n", buf); 137 | // 138 | sam3CloseSession(&ses); 139 | return 0; 140 | error: 141 | sam3CloseSession(&ses); 142 | return 1; 143 | } 144 | -------------------------------------------------------------------------------- /examples/sam3/dgrams.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | #define KEYFILE "dgrams.key" 35 | 36 | int main(int argc, char *argv[]) { 37 | Sam3Session ses; 38 | char privkey[SAM3_PRIVKEY_MAX_SIZE + 1], 39 | pubkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + 1], 40 | buf[33 * 1024]; 41 | 42 | /** quit command */ 43 | const char *quitstr = "quit"; 44 | const size_t quitlen = strlen(quitstr); 45 | 46 | /** reply response */ 47 | const char *replystr = "reply: "; 48 | const size_t replylen = strlen(replystr); 49 | 50 | FILE *fl; 51 | // 52 | libsam3_debug = 1; 53 | // 54 | 55 | /** generate new destination keypair */ 56 | printf("generating keys...\n"); 57 | if (sam3GenerateKeys(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 4) < 0) { 58 | fprintf(stderr, "FATAL: can't generate keys\n"); 59 | return 1; 60 | } 61 | /** copy keypair into local buffer */ 62 | strncpy(pubkey, ses.pubkey, sizeof(pubkey)); 63 | strncpy(privkey, ses.privkey, sizeof(privkey)); 64 | /** create sam session */ 65 | printf("creating session...\n"); 66 | if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, privkey, 67 | SAM3_SESSION_DGRAM, 4, NULL) < 0) { 68 | fprintf(stderr, "FATAL: can't create session\n"); 69 | return 1; 70 | } 71 | /** make sure we have the right destination */ 72 | // FIXME: probably not needed 73 | if (strcmp(pubkey, ses.pubkey) != 0) { 74 | fprintf(stderr, "FATAL: destination keys don't match\n"); 75 | sam3CloseSession(&ses); 76 | return 1; 77 | } 78 | /** print destination to stdout */ 79 | printf("PUB KEY (length = %li)\n=======\n%s\n=======\n", 80 | strlen(ses.pubkey), ses.pubkey); 81 | if ((fl = fopen(KEYFILE, "wb")) != NULL) { 82 | /** write public key to keyfile */ 83 | fwrite(pubkey, strlen(pubkey), 1, fl); 84 | fclose(fl); 85 | } 86 | 87 | /* now listen for UDP packets */ 88 | printf("starting main loop...\n"); 89 | for (;;) { 90 | /** save replylen bytes for out reply at begining */ 91 | char *datagramBuf = buf + replylen; 92 | const size_t datagramMaxLen = sizeof(buf) - replylen; 93 | int sz, isquit; 94 | printf("waiting for datagram...\n"); 95 | /** blocks until we get a UDP packet */ 96 | sz = sam3DatagramReceive(&ses, datagramBuf, datagramMaxLen); 97 | if (sz < 0) { 98 | fprintf(stderr, "ERROR: %s\n", ses.error); 99 | goto error; 100 | } 101 | /** ensure null terminated string */ 102 | datagramBuf[sz] = 0; 103 | /** print out datagram payload to user */ 104 | printf("FROM\n====\n%s\n====\n", ses.destkey); 105 | printf("SIZE=%d\n", sz); 106 | printf("data: [%s]\n", datagramBuf); 107 | /** check for "quit" */ 108 | isquit = (sz == quitlen && memcmp(datagramBuf, quitstr, quitlen) == 0); 109 | /** echo datagram back to sender with "reply: " at the beginning */ 110 | memcpy(buf, replystr, replylen); 111 | 112 | if (sam3DatagramSend(&ses, ses.destkey, buf, sz + replylen) < 0) { 113 | fprintf(stderr, "ERROR: %s\n", ses.error); 114 | goto error; 115 | } 116 | /** if we got a quit command wait for 10 seconds and break out of the 117 | * mainloop */ 118 | if (isquit) { 119 | printf("shutting down...\n"); 120 | sleep(10); /* let dgram reach it's destination */ 121 | break; 122 | } 123 | } 124 | /** close session and delete keyfile */ 125 | sam3CloseSession(&ses); 126 | unlink(KEYFILE); 127 | return 0; 128 | error: 129 | /** error case, close session, delete keyfile and return exit code 1 */ 130 | sam3CloseSession(&ses); 131 | unlink(KEYFILE); 132 | return 1; 133 | } 134 | -------------------------------------------------------------------------------- /examples/sam3/keys.c: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | #include "../libsam3/libsam3.h" 4 | #include 5 | 6 | int main() { 7 | // The session is only usef for transporting the data 8 | Sam3Session ss; 9 | 10 | if (0 > sam3GenerateKeys(&ss, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 4)) { 11 | printf("got error"); 12 | return -1; 13 | } 14 | printf("\tpubkey: %s \n \tprivkey: %s", ss.pubkey, ss.privkey); 15 | /*auto pub = std::string(ss.pubkey); 16 | auto priv = std::string(ss.privkey); 17 | 18 | std::cout << "pub " << pub << std::endl << "priv " << priv << std::endl;*/ 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /examples/sam3/keys.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../libsam3/libsam3.h" 5 | 6 | int main() { 7 | // The session is only usef for transporting the data 8 | Sam3Session ss; 9 | 10 | if (0 > sam3GenerateKeys(&ss, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, Sam3SigType::EdDSA_SHA512_Ed25519)) { 11 | printf("got error"); 12 | return -1; 13 | } 14 | auto pub = std::string(ss.pubkey); 15 | auto priv = std::string(ss.privkey); 16 | 17 | std::cout << "pub " << pub << std::endl << "priv " << priv << std::endl; 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /examples/sam3/namelookup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "../libsam3/libsam3.h" 32 | 33 | int main(int argc, char *argv[]) { 34 | Sam3Session ses; 35 | // 36 | // 37 | libsam3_debug = 1; 38 | // 39 | if (argc < 2) { 40 | printf("usage: %s name [name...]\n", argv[0]); 41 | return 1; 42 | } 43 | /** for each name in arguments ... */ 44 | for (int n = 1; n < argc; ++n) { 45 | if (!getenv("I2P_LOOKUP_QUIET")) { 46 | fprintf(stdout, "%s ... ", argv[n]); 47 | fflush(stdout); 48 | } 49 | /** do oneshot name lookup */ 50 | if (sam3NameLookup(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, argv[n]) >= 51 | 0) { 52 | fprintf(stdout, "%s\n\n", ses.destkey); 53 | } else { 54 | fprintf(stdout, "FAILED [%s]\n", ses.error); 55 | } 56 | } 57 | // 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/sam3/samtest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | int main(int argc, char *argv[]) { 35 | int fd; 36 | SAMFieldList *rep = NULL; 37 | const char *v; 38 | // 39 | libsam3_debug = 1; 40 | // 41 | // 42 | if ((fd = sam3Handshake(NULL, 0, NULL)) < 0) 43 | return 1; 44 | // 45 | if (sam3tcpPrintf(fd, "DEST GENERATE\n") < 0) 46 | goto error; 47 | rep = sam3ReadReply(fd); 48 | // sam3DumpFieldList(rep); 49 | if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PUB", NULL)) 50 | goto error; 51 | if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PRIV", NULL)) 52 | goto error; 53 | v = sam3FindField(rep, "PUB"); 54 | printf("PUB KEY\n=======\n%s\n", v); 55 | v = sam3FindField(rep, "PRIV"); 56 | printf("PRIV KEY\n========\n%s\n", v); 57 | sam3FreeFieldList(rep); 58 | rep = NULL; 59 | // 60 | sam3FreeFieldList(rep); 61 | sam3tcpDisconnect(fd); 62 | return 0; 63 | error: 64 | sam3FreeFieldList(rep); 65 | sam3tcpDisconnect(fd); 66 | return 1; 67 | } 68 | -------------------------------------------------------------------------------- /examples/sam3/streamc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | #define KEYFILE "streams.key" 35 | 36 | int main(int argc, char *argv[]) { 37 | Sam3Session ses; 38 | Sam3Connection *conn; 39 | char cmd[1024], destkey[617]; // 616 chars + \0 40 | // 41 | libsam3_debug = 1; 42 | // 43 | memset(destkey, 0, sizeof(destkey)); 44 | // 45 | if (argc < 2) { 46 | FILE *fl = fopen(KEYFILE, "rb"); 47 | // 48 | if (fl != NULL) { 49 | if (fread(destkey, 616, 1, fl) == 1) { 50 | fclose(fl); 51 | goto ok; 52 | } 53 | fclose(fl); 54 | } 55 | printf("usage: streamc PUBKEY\n"); 56 | return 1; 57 | } else { 58 | if (!sam3CheckValidKeyLength(argv[1])) { 59 | fprintf(stderr, "FATAL: invalid key length! %s %lu\n", argv[1], 60 | strlen(argv[1])); 61 | return 1; 62 | } 63 | strcpy(destkey, argv[1]); 64 | } 65 | // 66 | ok: 67 | printf("creating session...\n"); 68 | // create TRANSIENT session 69 | if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 70 | SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 4, 71 | NULL) < 0) { 72 | fprintf(stderr, "FATAL: can't create session\n"); 73 | return 1; 74 | } 75 | // 76 | printf("connecting...\n"); 77 | if ((conn = sam3StreamConnect(&ses, destkey)) == NULL) { 78 | fprintf(stderr, "FATAL: can't connect: %s\n", ses.error); 79 | sam3CloseSession(&ses); 80 | return 1; 81 | } 82 | // 83 | // now waiting for incoming connection 84 | printf("sending test command...\n"); 85 | if (sam3tcpPrintf(conn->fd, "test\n") < 0) 86 | goto error; 87 | if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) 88 | goto error; 89 | printf("echo: %s\n", cmd); 90 | // 91 | printf("sending quit command...\n"); 92 | if (sam3tcpPrintf(conn->fd, "quit\n") < 0) 93 | goto error; 94 | // 95 | sam3CloseConnection(conn); 96 | sam3CloseSession(&ses); 97 | return 0; 98 | error: 99 | fprintf(stderr, "FATAL: some error occured!\n"); 100 | sam3CloseConnection(conn); 101 | sam3CloseSession(&ses); 102 | return 1; 103 | } 104 | -------------------------------------------------------------------------------- /examples/sam3/streamcs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | #define KEYFILE "streams.key" 35 | 36 | int main(int argc, char *argv[]) { 37 | Sam3Session ses; 38 | Sam3Connection *conn; 39 | char cmd[1024], destkey[617]; // 616 chars + \0 40 | // 41 | libsam3_debug = 1; 42 | // 43 | memset(destkey, 0, sizeof(destkey)); 44 | // 45 | if (argc < 2) { 46 | FILE *fl = fopen(KEYFILE, "rb"); 47 | // 48 | if (fl != NULL) { 49 | if (fread(destkey, 616, 1, fl) == 1) { 50 | fclose(fl); 51 | goto ok; 52 | } 53 | fclose(fl); 54 | } 55 | printf("usage: streamc PUBKEY\n"); 56 | return 1; 57 | } else { 58 | if (!sam3CheckValidKeyLength(argv[1])) { 59 | fprintf(stderr, "FATAL: invalid key length! %s %lu\n", argv[1], 60 | strlen(argv[1])); 61 | return 1; 62 | } 63 | strcpy(destkey, argv[1]); 64 | } 65 | // 66 | ok: 67 | printf("creating session...\n"); 68 | // create TRANSIENT session 69 | if (sam3CreateSilentSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 70 | SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 71 | 4, NULL) < 0) { 72 | fprintf(stderr, "FATAL: can't create session\n"); 73 | return 1; 74 | } 75 | // 76 | printf("connecting...\n"); 77 | if ((conn = sam3StreamConnect(&ses, destkey)) == NULL) { 78 | fprintf(stderr, "FATAL: can't connect: %s\n", ses.error); 79 | sam3CloseSession(&ses); 80 | return 1; 81 | } 82 | // 83 | // now waiting for incoming connection 84 | printf("sending test command...\n"); 85 | if (sam3tcpPrintf(conn->fd, "test\n") < 0) 86 | goto error; 87 | if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) 88 | goto error; 89 | printf("echo: %s\n", cmd); 90 | // 91 | printf("sending quit command...\n"); 92 | if (sam3tcpPrintf(conn->fd, "quit\n") < 0) 93 | goto error; 94 | // 95 | sam3CloseConnection(conn); 96 | sam3CloseSession(&ses); 97 | return 0; 98 | error: 99 | fprintf(stderr, "FATAL: some error occured!\n"); 100 | sam3CloseConnection(conn); 101 | sam3CloseSession(&ses); 102 | return 1; 103 | } 104 | -------------------------------------------------------------------------------- /examples/sam3/streams.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | #define KEYFILE "streams.key" 35 | 36 | int main(int argc, char *argv[]) { 37 | Sam3Session ses; 38 | Sam3Connection *conn; 39 | FILE *fl; 40 | // 41 | libsam3_debug = 1; 42 | // 43 | printf("creating session...\n"); 44 | // create TRANSIENT session 45 | if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 46 | SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 4, 47 | NULL) < 0) { 48 | fprintf(stderr, "FATAL: can't create session\n"); 49 | return 1; 50 | } 51 | // 52 | printf("PUB KEY\n=======\n%s\n=======\n", ses.pubkey); 53 | if ((fl = fopen(KEYFILE, "wb")) != NULL) { 54 | fwrite(ses.pubkey, strlen(ses.pubkey), 1, fl); 55 | fclose(fl); 56 | } 57 | // 58 | printf("starting stream acceptor...\n"); 59 | if ((conn = sam3StreamAccept(&ses)) == NULL) { 60 | fprintf(stderr, "FATAL: can't accept: %s\n", ses.error); 61 | sam3CloseSession(&ses); 62 | return 1; 63 | } 64 | printf("FROM\n====\n%s\n====\n", conn->destkey); 65 | // 66 | printf("starting main loop...\n"); 67 | for (;;) { 68 | char cmd[256]; 69 | // 70 | if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) 71 | goto error; 72 | printf("cmd: [%s]\n", cmd); 73 | if (strcmp(cmd, "quit") == 0) 74 | break; 75 | // echo command 76 | if (sam3tcpPrintf(conn->fd, "re: %s\n", cmd) < 0) 77 | goto error; 78 | } 79 | // 80 | sam3CloseSession(&ses); 81 | unlink(KEYFILE); 82 | return 0; 83 | error: 84 | fprintf(stderr, "FATAL: some error occured!\n"); 85 | sam3CloseSession(&ses); 86 | unlink(KEYFILE); 87 | return 1; 88 | } 89 | -------------------------------------------------------------------------------- /examples/sam3/streamss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../libsam3/libsam3.h" 33 | 34 | #define KEYFILE "streams.key" 35 | 36 | int main(int argc, char *argv[]) { 37 | Sam3Session ses; 38 | Sam3Connection *conn; 39 | FILE *fl; 40 | // 41 | libsam3_debug = 1; 42 | // 43 | printf("creating session...\n"); 44 | // create TRANSIENT session 45 | if (sam3CreateSilentSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 46 | SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 47 | 4, NULL) < 0) { 48 | fprintf(stderr, "FATAL: can't create session\n"); 49 | return 1; 50 | } 51 | // 52 | printf("PUB KEY\n=======\n%s\n=======\n", ses.pubkey); 53 | if ((fl = fopen(KEYFILE, "wb")) != NULL) { 54 | fwrite(ses.pubkey, strlen(ses.pubkey), 1, fl); 55 | fclose(fl); 56 | } 57 | // 58 | printf("starting stream acceptor...\n"); 59 | if ((conn = sam3StreamAccept(&ses)) == NULL) { 60 | fprintf(stderr, "FATAL: can't accept: %s\n", ses.error); 61 | sam3CloseSession(&ses); 62 | return 1; 63 | } 64 | printf("FROM\n====\n%s\n====\n", conn->destkey); 65 | // 66 | printf("starting main loop...\n"); 67 | for (;;) { 68 | char cmd[256]; 69 | // 70 | if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) 71 | goto error; 72 | printf("cmd: [%s]\n", cmd); 73 | if (strcmp(cmd, "quit") == 0) 74 | break; 75 | // echo command 76 | if (sam3tcpPrintf(conn->fd, "re: %s\n", cmd) < 0) 77 | goto error; 78 | } 79 | // 80 | sam3CloseSession(&ses); 81 | unlink(KEYFILE); 82 | return 0; 83 | error: 84 | fprintf(stderr, "FATAL: some error occured!\n"); 85 | sam3CloseSession(&ses); 86 | unlink(KEYFILE); 87 | return 1; 88 | } 89 | -------------------------------------------------------------------------------- /examples/sam3a/test00.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "../libsam3a/libsam3a.h" 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | static void scbErrorClose(Sam3ASession *ses) { 40 | fprintf(stderr, 41 | "\n===============================\nSESION_ERROR: " 42 | "[%s]\n===============================\n", 43 | ses->error); 44 | sam3aCloseSession(ses); // it's safe here 45 | } 46 | 47 | static void scbNRCreated(Sam3ASession *ses) { 48 | fprintf(stderr, "\n===============================\nNAME RESOLVED: [%s]\n", 49 | ses->params); 50 | fprintf(stderr, "PUB: %s\n===============================\n", ses->destkey); 51 | sam3aCloseSession(ses); // it's safe here 52 | } 53 | 54 | static const Sam3ASessionCallbacks scbNR = { 55 | .cbError = scbErrorClose, 56 | .cbCreated = scbNRCreated, 57 | .cbDisconnected = NULL, 58 | .cbDatagramRead = NULL, 59 | .cbDestroy = NULL, 60 | }; 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | static void scbKGCreated(Sam3ASession *ses) { 64 | fprintf(stderr, "\n===============================\nKEYS GENERATED\n"); 65 | fprintf(stderr, "\rPRIV: %s\n", ses->privkey); 66 | fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); 67 | sam3aCloseSession(ses); // it's safe here 68 | } 69 | 70 | static const Sam3ASessionCallbacks scbKG = { 71 | .cbError = scbErrorClose, 72 | .cbCreated = scbKGCreated, 73 | .cbDisconnected = NULL, 74 | .cbDatagramRead = NULL, 75 | .cbDestroy = NULL, 76 | }; 77 | 78 | //////////////////////////////////////////////////////////////////////////////// 79 | static void scbError(Sam3ASession *ses) { 80 | fprintf(stderr, 81 | "\n===============================\nSESION_ERROR: " 82 | "[%s]\n===============================\n", 83 | ses->error); 84 | } 85 | 86 | static void scbCreated(Sam3ASession *ses) { 87 | fprintf(stderr, "\n===============================\nSESION_CREATED\n"); 88 | fprintf(stderr, "\rPRIV: %s\n", ses->privkey); 89 | fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); 90 | sam3aCancelSession(ses); // it's safe here 91 | } 92 | 93 | static void scbDisconnected(Sam3ASession *ses) { 94 | fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" 95 | "===========================\n"); 96 | } 97 | 98 | static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { 99 | fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" 100 | "============================\n"); 101 | } 102 | 103 | static void scbDestroy(Sam3ASession *ses) { 104 | fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" 105 | "========================\n"); 106 | } 107 | 108 | /** callbacks for our SAM session */ 109 | static const Sam3ASessionCallbacks scb = { 110 | .cbError = scbError, 111 | .cbCreated = scbCreated, 112 | .cbDisconnected = scbDisconnected, 113 | .cbDatagramRead = scbDGramRead, 114 | .cbDestroy = scbDestroy, 115 | }; 116 | 117 | //////////////////////////////////////////////////////////////////////////////// 118 | #define HOST SAM3A_HOST_DEFAULT 119 | //#define HOST "google.com" 120 | 121 | int main(int argc, char *argv[]) { 122 | Sam3ASession ses, snr, skg; 123 | // 124 | // libsam3a_debug = 1; 125 | // 126 | if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, 127 | SAM3A_DESTINATION_TRANSIENT, 128 | SAM3A_SESSION_STREAM) < 0) { 129 | fprintf(stderr, "FATAL: can't create main session!\n"); 130 | return 1; 131 | } 132 | // generate keys 133 | if (sam3aGenerateKeys(&skg, &scbKG, HOST, SAM3A_PORT_DEFAULT) < 0) { 134 | sam3aCloseSession(&ses); 135 | fprintf(stderr, "FATAL: can't create keygen session!\n"); 136 | return 1; 137 | } 138 | // do a name lookup for zzz.i2p 139 | if (sam3aNameLookup(&snr, &scbNR, HOST, SAM3A_PORT_DEFAULT, "zzz.i2p") < 0) { 140 | sam3aCloseSession(&skg); 141 | sam3aCloseSession(&ses); 142 | fprintf(stderr, "FATAL: can't create name resolving session!\n"); 143 | return 1; 144 | } 145 | // while we have sessions ... 146 | while (sam3aIsActiveSession(&ses) || sam3aIsActiveSession(&snr) || 147 | sam3aIsActiveSession(&skg)) { 148 | fd_set rds, wrs; 149 | int res, maxfd = 0; 150 | struct timeval to; 151 | // set up file descriptors for select() 152 | FD_ZERO(&rds); 153 | FD_ZERO(&wrs); 154 | // obtain the maximum fd for select() 155 | if (sam3aIsActiveSession(&ses) && 156 | (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) 157 | break; 158 | if (sam3aIsActiveSession(&snr) && 159 | (maxfd = sam3aAddSessionToFDS(&snr, -1, &rds, &wrs)) < 0) 160 | break; 161 | if (sam3aIsActiveSession(&skg) && 162 | (maxfd = sam3aAddSessionToFDS(&skg, -1, &rds, &wrs)) < 0) 163 | break; 164 | // set timeout to 1 second 165 | sam3ams2timeval(&to, 1000); 166 | // call select() 167 | res = select(maxfd + 1, &rds, &wrs, NULL, &to); 168 | if (res < 0) { 169 | if (errno == EINTR) 170 | continue; 171 | fprintf(stderr, "FATAL: select() error!\n"); 172 | break; 173 | } 174 | if (res == 0) { 175 | // idle, no activity 176 | fprintf(stdout, "."); 177 | fflush(stdout); 178 | } else { 179 | // we have activity, process io 180 | if (sam3aIsActiveSession(&ses)) 181 | sam3aProcessSessionIO(&ses, &rds, &wrs); 182 | if (sam3aIsActiveSession(&snr)) 183 | sam3aProcessSessionIO(&snr, &rds, &wrs); 184 | if (sam3aIsActiveSession(&skg)) 185 | sam3aProcessSessionIO(&skg, &rds, &wrs); 186 | } 187 | } 188 | // close seessions 189 | sam3aCloseSession(&ses); 190 | sam3aCloseSession(&skg); 191 | sam3aCloseSession(&snr); 192 | // exit 193 | return 0; 194 | } 195 | -------------------------------------------------------------------------------- /examples/sam3a/test_sc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "../libsam3a/libsam3a.h" 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | #define KEYFILE "streams.key" 40 | 41 | //////////////////////////////////////////////////////////////////////////////// 42 | static void ccbError(Sam3AConnection *ct) { 43 | fprintf(stderr, 44 | "\n===============================\nCONNECTION_ERROR: " 45 | "[%s]\n===============================\n", 46 | ct->error); 47 | } 48 | 49 | static void ccbDisconnected(Sam3AConnection *ct) { 50 | fprintf(stderr, "\n===============================\nCONNECTION_" 51 | "DISCONNECTED\n===============================\n"); 52 | } 53 | 54 | static void ccbConnected(Sam3AConnection *ct) { 55 | fprintf(stderr, "\n===============================\nCONNECTION_CONNECTED\n===" 56 | "============================\n"); 57 | // sam3aCancelConnection(ct); // cbSent() will not be called 58 | } 59 | 60 | static void ccbAccepted(Sam3AConnection *ct) { 61 | fprintf(stderr, "\n===============================\nCONNECTION_ACCEPTED\n====" 62 | "===========================\n"); 63 | } 64 | 65 | static void ccbSent(Sam3AConnection *ct) { 66 | fprintf(stderr, "\n===============================\nCONNECTION_WANTBYTES\n===" 67 | "============================\n"); 68 | // sam3aCancelConnection(ct); 69 | // sam3aCancelSession(ct->ses); // hehe 70 | fprintf(stderr, "(%p)\n", ct->udata); 71 | // 72 | switch ((intptr_t)ct->udata) { 73 | case 0: 74 | if (sam3aSend(ct, "test\n", -1) < 0) { 75 | fprintf(stderr, "SEND ERROR!\n"); 76 | sam3aCancelSession(ct->ses); // hehe 77 | } 78 | break; 79 | case 1: 80 | if (sam3aSend(ct, "quit\n", -1) < 0) { 81 | fprintf(stderr, "SEND ERROR!\n"); 82 | sam3aCancelSession(ct->ses); // hehe 83 | } 84 | break; 85 | default: 86 | return; 87 | } 88 | ct->udata = (void *)(((intptr_t)ct->udata) + 1); 89 | } 90 | 91 | static void ccbRead(Sam3AConnection *ct, const void *buf, int bufsize) { 92 | fprintf(stderr, 93 | "\n===============================\nCONNECTION_GOTBYTES " 94 | "(%d)\n===============================\n", 95 | bufsize); 96 | } 97 | 98 | static void ccbDestroy(Sam3AConnection *ct) { 99 | fprintf(stderr, "\n===============================\nCONNECTION_DESTROY\n=====" 100 | "==========================\n"); 101 | } 102 | 103 | static const Sam3AConnectionCallbacks ccb = { 104 | .cbError = ccbError, 105 | .cbDisconnected = ccbDisconnected, 106 | .cbConnected = ccbConnected, 107 | .cbAccepted = ccbAccepted, 108 | .cbSent = ccbSent, 109 | .cbRead = ccbRead, 110 | .cbDestroy = ccbDestroy, 111 | }; 112 | 113 | //////////////////////////////////////////////////////////////////////////////// 114 | static void scbError(Sam3ASession *ses) { 115 | fprintf(stderr, 116 | "\n===============================\nSESION_ERROR: " 117 | "[%s]\n===============================\n", 118 | ses->error); 119 | } 120 | 121 | static void scbCreated(Sam3ASession *ses) { 122 | char destkey[517]; 123 | FILE *fl; 124 | // 125 | fprintf(stderr, "\n===============================\nSESION_CREATED\n"); 126 | fprintf(stderr, "\rPRIV: %s\n", ses->privkey); 127 | fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); 128 | // 129 | fl = fopen(KEYFILE, "rb"); 130 | // 131 | if (fl == NULL) { 132 | fprintf(stderr, "ERROR: NO KEY FILE!\n"); 133 | sam3aCancelSession(ses); 134 | return; 135 | } 136 | if (fread(destkey, 516, 1, fl) != 1) { 137 | fprintf(stderr, "ERROR: INVALID KEY FILE!\n"); 138 | fclose(fl); 139 | sam3aCancelSession(ses); 140 | return; 141 | } 142 | fclose(fl); 143 | destkey[516] = 0; 144 | if (sam3aStreamConnect(ses, &ccb, destkey) == NULL) { 145 | fprintf(stderr, "ERROR: CAN'T CREATE CONNECTION!\n"); 146 | sam3aCancelSession(ses); 147 | return; 148 | } 149 | fprintf(stderr, "GOON: creating connection...\n"); 150 | } 151 | 152 | static void scbDisconnected(Sam3ASession *ses) { 153 | fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" 154 | "===========================\n"); 155 | } 156 | 157 | static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { 158 | fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" 159 | "============================\n"); 160 | } 161 | 162 | static void scbDestroy(Sam3ASession *ses) { 163 | fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" 164 | "========================\n"); 165 | } 166 | 167 | static const Sam3ASessionCallbacks scb = { 168 | .cbError = scbError, 169 | .cbCreated = scbCreated, 170 | .cbDisconnected = scbDisconnected, 171 | .cbDatagramRead = scbDGramRead, 172 | .cbDestroy = scbDestroy, 173 | }; 174 | 175 | //////////////////////////////////////////////////////////////////////////////// 176 | #define HOST SAM3A_HOST_DEFAULT 177 | //#define HOST "google.com" 178 | 179 | int main(int argc, char *argv[]) { 180 | Sam3ASession ses; 181 | // 182 | libsam3a_debug = 0; 183 | // 184 | if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, 185 | SAM3A_DESTINATION_TRANSIENT, 186 | SAM3A_SESSION_STREAM) < 0) { 187 | fprintf(stderr, "FATAL: can't create main session!\n"); 188 | return 1; 189 | } 190 | // 191 | while (sam3aIsActiveSession(&ses)) { 192 | fd_set rds, wrs; 193 | int res, maxfd = 0; 194 | struct timeval to; 195 | // 196 | FD_ZERO(&rds); 197 | FD_ZERO(&wrs); 198 | if (sam3aIsActiveSession(&ses) && 199 | (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) 200 | break; 201 | sam3ams2timeval(&to, 1000); 202 | res = select(maxfd + 1, &rds, &wrs, NULL, &to); 203 | if (res < 0) { 204 | if (errno == EINTR) 205 | continue; 206 | fprintf(stderr, "FATAL: select() error!\n"); 207 | break; 208 | } 209 | if (res == 0) { 210 | fprintf(stdout, "."); 211 | fflush(stdout); 212 | } else { 213 | if (sam3aIsActiveSession(&ses)) 214 | sam3aProcessSessionIO(&ses, &rds, &wrs); 215 | } 216 | } 217 | // 218 | sam3aCloseSession(&ses); 219 | // 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /examples/sam3a/test_ss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "../libsam3a/libsam3a.h" 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | #define KEYFILE "streams.key" 40 | 41 | //////////////////////////////////////////////////////////////////////////////// 42 | typedef struct { 43 | char *str; 44 | int strsize; 45 | int strused; 46 | int doQuit; 47 | } ConnData; 48 | 49 | static void cdAppendChar(ConnData *d, char ch) { 50 | if (d->strused + 1 >= d->strsize) { 51 | // fuck errors 52 | d->strsize = d->strused + 1024; 53 | d->str = realloc(d->str, d->strsize + 1); 54 | } 55 | d->str[d->strused++] = ch; 56 | d->str[d->strused] = 0; 57 | } 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | static void ccbError(Sam3AConnection *ct) { 61 | fprintf(stderr, 62 | "\n===============================\nCONNECTION_ERROR: " 63 | "[%s]\n===============================\n", 64 | ct->error); 65 | } 66 | 67 | static void ccbDisconnected(Sam3AConnection *ct) { 68 | fprintf(stderr, "\n===============================\nCONNECTION_" 69 | "DISCONNECTED\n===============================\n"); 70 | } 71 | 72 | static void ccbConnected(Sam3AConnection *ct) { 73 | fprintf(stderr, "\n===============================\nCONNECTION_CONNECTED\n===" 74 | "============================\n"); 75 | // sam3aCancelConnection(ct); // cbSent() will not be called 76 | } 77 | 78 | static void ccbAccepted(Sam3AConnection *ct) { 79 | fprintf(stderr, "\n===============================\nCONNECTION_ACCEPTED\n====" 80 | "===========================\n"); 81 | fprintf(stderr, "FROM: %s\n===============================\n", ct->destkey); 82 | } 83 | 84 | static void ccbSent(Sam3AConnection *ct) { 85 | ConnData *d = (ConnData *)ct->udata; 86 | // 87 | fprintf(stderr, "\n===============================\nCONNECTION_WANTBYTES\n===" 88 | "============================\n"); 89 | if (d->doQuit) { 90 | sam3aCancelSession(ct->ses); // hehe 91 | } 92 | } 93 | 94 | static void ccbRead(Sam3AConnection *ct, const void *buf, int bufsize) { 95 | const char *b = (const char *)buf; 96 | ConnData *d = (ConnData *)ct->udata; 97 | // 98 | fprintf(stderr, 99 | "\n===============================\nCONNECTION_GOTBYTES " 100 | "(%d)\n===============================\n", 101 | bufsize); 102 | while (bufsize > 0) { 103 | cdAppendChar(ct->udata, *b); 104 | if (*b == '\n') { 105 | fprintf(stderr, "cmd: %s", d->str); 106 | if (strcasecmp(d->str, "quit\n") == 0) 107 | d->doQuit = 1; 108 | if (sam3aSend(ct, d->str, -1) < 0) { 109 | // sam3aCancelConnection(ct); // hehe 110 | sam3aCancelSession(ct->ses); // hehe 111 | return; 112 | } 113 | d->str[0] = 0; 114 | d->strused = 0; 115 | } 116 | ++b; 117 | --bufsize; 118 | } 119 | } 120 | 121 | static void ccbDestroy(Sam3AConnection *ct) { 122 | fprintf(stderr, "\n===============================\nCONNECTION_DESTROY\n=====" 123 | "==========================\n"); 124 | if (ct->udata != NULL) { 125 | ConnData *d = (ConnData *)ct->udata; 126 | // 127 | if (d->str != NULL) 128 | free(d->str); 129 | free(d); 130 | } 131 | } 132 | 133 | static const Sam3AConnectionCallbacks ccb = { 134 | .cbError = ccbError, 135 | .cbDisconnected = ccbDisconnected, 136 | .cbConnected = ccbConnected, 137 | .cbAccepted = ccbAccepted, 138 | .cbSent = ccbSent, 139 | .cbRead = ccbRead, 140 | .cbDestroy = ccbDestroy, 141 | }; 142 | 143 | //////////////////////////////////////////////////////////////////////////////// 144 | static void scbError(Sam3ASession *ses) { 145 | fprintf(stderr, 146 | "\n===============================\nSESION_ERROR: " 147 | "[%s]\n===============================\n", 148 | ses->error); 149 | } 150 | 151 | static void scbCreated(Sam3ASession *ses) { 152 | FILE *fl; 153 | Sam3AConnection *conn; 154 | // 155 | fprintf(stderr, "\n===============================\nSESION_CREATED\n"); 156 | fprintf(stderr, "\rPRIV: %s\n", ses->privkey); 157 | fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); 158 | // 159 | fl = fopen(KEYFILE, "wb"); 160 | // 161 | if (fl == NULL) { 162 | fprintf(stderr, "ERROR: CAN'T CREATE KEY FILE!\n"); 163 | sam3aCancelSession(ses); 164 | return; 165 | } 166 | if (fwrite(ses->pubkey, 516, 1, fl) != 1) { 167 | fprintf(stderr, "ERROR: CAN'T WRITE KEY FILE!\n"); 168 | fclose(fl); 169 | sam3aCancelSession(ses); 170 | return; 171 | } 172 | fclose(fl); 173 | if ((conn = sam3aStreamAccept(ses, &ccb)) == NULL) { 174 | fprintf(stderr, "ERROR: CAN'T CREATE CONNECTION!\n"); 175 | sam3aCancelSession(ses); 176 | return; 177 | } 178 | // 179 | conn->udata = calloc(1, sizeof(ConnData)); 180 | fprintf(stderr, "GOON: accepting connection...\n"); 181 | } 182 | 183 | static void scbDisconnected(Sam3ASession *ses) { 184 | fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" 185 | "===========================\n"); 186 | } 187 | 188 | static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { 189 | fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" 190 | "============================\n"); 191 | } 192 | 193 | static void scbDestroy(Sam3ASession *ses) { 194 | fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" 195 | "========================\n"); 196 | } 197 | 198 | static const Sam3ASessionCallbacks scb = { 199 | .cbError = scbError, 200 | .cbCreated = scbCreated, 201 | .cbDisconnected = scbDisconnected, 202 | .cbDatagramRead = scbDGramRead, 203 | .cbDestroy = scbDestroy, 204 | }; 205 | 206 | //////////////////////////////////////////////////////////////////////////////// 207 | #define HOST SAM3A_HOST_DEFAULT 208 | //#define HOST "google.com" 209 | 210 | int main(int argc, char *argv[]) { 211 | Sam3ASession ses; 212 | // 213 | libsam3a_debug = 0; 214 | // 215 | if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, 216 | SAM3A_DESTINATION_TRANSIENT, 217 | SAM3A_SESSION_STREAM) < 0) { 218 | fprintf(stderr, "FATAL: can't create main session!\n"); 219 | return 1; 220 | } 221 | // 222 | while (sam3aIsActiveSession(&ses)) { 223 | fd_set rds, wrs; 224 | int res, maxfd = 0; 225 | struct timeval to; 226 | // 227 | FD_ZERO(&rds); 228 | FD_ZERO(&wrs); 229 | if (sam3aIsActiveSession(&ses) && 230 | (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) 231 | break; 232 | sam3ams2timeval(&to, 1000); 233 | res = select(maxfd + 1, &rds, &wrs, NULL, &to); 234 | if (res < 0) { 235 | if (errno == EINTR) 236 | continue; 237 | fprintf(stderr, "FATAL: select() error!\n"); 238 | break; 239 | } 240 | if (res == 0) { 241 | fprintf(stdout, "."); 242 | fflush(stdout); 243 | } else { 244 | if (sam3aIsActiveSession(&ses)) 245 | sam3aProcessSessionIO(&ses, &rds, &wrs); 246 | } 247 | } 248 | // 249 | sam3aCloseSession(&ses); 250 | // 251 | return 0; 252 | } 253 | -------------------------------------------------------------------------------- /src/ext/tinytest.c: -------------------------------------------------------------------------------- 1 | /* tinytest.c -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | #ifdef TINYTEST_LOCAL 26 | #include "tinytest_local.h" 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifndef NO_FORKING 35 | 36 | #ifdef _WIN32 37 | #include 38 | #else 39 | #include 40 | #include 41 | #include 42 | #endif 43 | 44 | #if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) 45 | #if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \ 46 | __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070) 47 | /* Workaround for a stupid bug in OSX 10.6 */ 48 | #define FORK_BREAKS_GCOV 49 | #include 50 | #endif 51 | #endif 52 | 53 | #endif /* !NO_FORKING */ 54 | 55 | #ifndef __GNUC__ 56 | #define __attribute__(x) 57 | #endif 58 | 59 | #include "tinytest.h" 60 | #include "tinytest_macros.h" 61 | 62 | #define LONGEST_TEST_NAME 16384 63 | 64 | static int in_tinytest_main = 0; /**< true if we're in tinytest_main().*/ 65 | static int n_ok = 0; /**< Number of tests that have passed */ 66 | static int n_bad = 0; /**< Number of tests that have failed. */ 67 | static int n_skipped = 0; /**< Number of tests that have been skipped. */ 68 | 69 | static int opt_forked = 0; /**< True iff we're called from inside a win32 fork*/ 70 | static int opt_nofork = 0; /**< Suppress calls to fork() for debugging. */ 71 | static int opt_verbosity = 1; /**< -==quiet,0==terse,1==normal,2==verbose */ 72 | const char *verbosity_flag = ""; 73 | 74 | const struct testlist_alias_t *cfg_aliases = NULL; 75 | 76 | enum outcome { SKIP = 2, OK = 1, FAIL = 0 }; 77 | static enum outcome cur_test_outcome = FAIL; 78 | const char *cur_test_prefix = NULL; /**< prefix of the current test group */ 79 | /** Name of the current test, if we haven't logged is yet. Used for --quiet */ 80 | const char *cur_test_name = NULL; 81 | 82 | #ifdef _WIN32 83 | /* Copy of argv[0] for win32. */ 84 | static char commandname[MAX_PATH + 1]; 85 | #endif 86 | 87 | static void usage(struct testgroup_t *groups, int list_groups) 88 | __attribute__((noreturn)); 89 | static int process_test_option(struct testgroup_t *groups, const char *test); 90 | 91 | static enum outcome testcase_run_bare_(const struct testcase_t *testcase) { 92 | void *env = NULL; 93 | enum outcome outcome; 94 | if (testcase->setup) { 95 | env = testcase->setup->setup_fn(testcase); 96 | if (!env) 97 | return FAIL; 98 | else if (env == (void *)TT_SKIP) 99 | return SKIP; 100 | } 101 | 102 | cur_test_outcome = OK; 103 | testcase->fn(env); 104 | outcome = cur_test_outcome; 105 | 106 | if (testcase->setup) { 107 | if (testcase->setup->cleanup_fn(testcase, env) == 0) 108 | outcome = FAIL; 109 | } 110 | 111 | return outcome; 112 | } 113 | 114 | #define MAGIC_EXITCODE 42 115 | 116 | #ifndef NO_FORKING 117 | 118 | static enum outcome testcase_run_forked_(const struct testgroup_t *group, 119 | const struct testcase_t *testcase) { 120 | #ifdef _WIN32 121 | /* Fork? On Win32? How primitive! We'll do what the smart kids do: 122 | we'll invoke our own exe (whose name we recall from the command 123 | line) with a command line that tells it to run just the test we 124 | want, and this time without forking. 125 | 126 | (No, threads aren't an option. The whole point of forking is to 127 | share no state between tests.) 128 | */ 129 | int ok; 130 | char buffer[LONGEST_TEST_NAME + 256]; 131 | STARTUPINFOA si; 132 | PROCESS_INFORMATION info; 133 | DWORD exitcode; 134 | 135 | if (!in_tinytest_main) { 136 | printf("\nERROR. On Windows, testcase_run_forked_ must be" 137 | " called from within tinytest_main.\n"); 138 | abort(); 139 | } 140 | if (opt_verbosity > 0) 141 | printf("[forking] "); 142 | 143 | snprintf(buffer, sizeof(buffer), "%s --RUNNING-FORKED %s %s%s", commandname, 144 | verbosity_flag, group->prefix, testcase->name); 145 | 146 | memset(&si, 0, sizeof(si)); 147 | memset(&info, 0, sizeof(info)); 148 | si.cb = sizeof(si); 149 | 150 | ok = CreateProcessA(commandname, buffer, NULL, NULL, 0, 0, NULL, NULL, &si, 151 | &info); 152 | if (!ok) { 153 | printf("CreateProcess failed!\n"); 154 | return 0; 155 | } 156 | WaitForSingleObject(info.hProcess, INFINITE); 157 | GetExitCodeProcess(info.hProcess, &exitcode); 158 | CloseHandle(info.hProcess); 159 | CloseHandle(info.hThread); 160 | if (exitcode == 0) 161 | return OK; 162 | else if (exitcode == MAGIC_EXITCODE) 163 | return SKIP; 164 | else 165 | return FAIL; 166 | #else 167 | int outcome_pipe[2]; 168 | pid_t pid; 169 | (void)group; 170 | 171 | if (pipe(outcome_pipe)) 172 | perror("opening pipe"); 173 | 174 | if (opt_verbosity > 0) 175 | printf("[forking] "); 176 | pid = fork(); 177 | #ifdef FORK_BREAKS_GCOV 178 | vproc_transaction_begin(0); 179 | #endif 180 | if (!pid) { 181 | /* child. */ 182 | int test_r, write_r; 183 | char b[1]; 184 | close(outcome_pipe[0]); 185 | test_r = testcase_run_bare_(testcase); 186 | assert(0 <= (int)test_r && (int)test_r <= 2); 187 | b[0] = "NYS"[test_r]; 188 | write_r = (int)write(outcome_pipe[1], b, 1); 189 | if (write_r != 1) { 190 | perror("write outcome to pipe"); 191 | exit(1); 192 | } 193 | exit(0); 194 | return FAIL; /* unreachable */ 195 | } else { 196 | /* parent */ 197 | int status, r; 198 | char b[1]; 199 | /* Close this now, so that if the other side closes it, 200 | * our read fails. */ 201 | close(outcome_pipe[1]); 202 | r = (int)read(outcome_pipe[0], b, 1); 203 | if (r == 0) { 204 | printf("[Lost connection!] "); 205 | return FAIL; 206 | } else if (r != 1) { 207 | perror("read outcome from pipe"); 208 | } 209 | waitpid(pid, &status, 0); 210 | close(outcome_pipe[0]); 211 | return b[0] == 'Y' ? OK : (b[0] == 'S' ? SKIP : FAIL); 212 | } 213 | #endif 214 | } 215 | 216 | #endif /* !NO_FORKING */ 217 | 218 | int testcase_run_one(const struct testgroup_t *group, 219 | const struct testcase_t *testcase) { 220 | enum outcome outcome; 221 | 222 | if (testcase->flags & (TT_SKIP | TT_OFF_BY_DEFAULT)) { 223 | if (opt_verbosity > 0) 224 | printf("%s%s: %s\n", group->prefix, testcase->name, 225 | (testcase->flags & TT_SKIP) ? "SKIPPED" : "DISABLED"); 226 | ++n_skipped; 227 | return SKIP; 228 | } 229 | 230 | if (opt_verbosity > 0 && !opt_forked) { 231 | printf("%s%s: ", group->prefix, testcase->name); 232 | } else { 233 | if (opt_verbosity == 0) 234 | printf("."); 235 | cur_test_prefix = group->prefix; 236 | cur_test_name = testcase->name; 237 | } 238 | 239 | #ifndef NO_FORKING 240 | if ((testcase->flags & TT_FORK) && !(opt_forked || opt_nofork)) { 241 | outcome = testcase_run_forked_(group, testcase); 242 | } else { 243 | #else 244 | { 245 | #endif 246 | outcome = testcase_run_bare_(testcase); 247 | } 248 | 249 | if (outcome == OK) { 250 | ++n_ok; 251 | if (opt_verbosity > 0 && !opt_forked) 252 | puts(opt_verbosity == 1 ? "OK" : ""); 253 | } else if (outcome == SKIP) { 254 | ++n_skipped; 255 | if (opt_verbosity > 0 && !opt_forked) 256 | puts("SKIPPED"); 257 | } else { 258 | ++n_bad; 259 | if (!opt_forked) 260 | printf("\n [%s FAILED]\n", testcase->name); 261 | } 262 | 263 | if (opt_forked) { 264 | exit(outcome == OK ? 0 : (outcome == SKIP ? MAGIC_EXITCODE : 1)); 265 | return 1; /* unreachable */ 266 | } else { 267 | return (int)outcome; 268 | } 269 | } 270 | 271 | int tinytest_set_flag_(struct testgroup_t *groups, const char *arg, int set, 272 | unsigned long flag) { 273 | int i, j; 274 | size_t length = LONGEST_TEST_NAME; 275 | char fullname[LONGEST_TEST_NAME]; 276 | int found = 0; 277 | if (strstr(arg, "..")) 278 | length = strstr(arg, "..") - arg; 279 | for (i = 0; groups[i].prefix; ++i) { 280 | for (j = 0; groups[i].cases[j].name; ++j) { 281 | struct testcase_t *testcase = &groups[i].cases[j]; 282 | snprintf(fullname, sizeof(fullname), "%s%s", groups[i].prefix, 283 | testcase->name); 284 | if (!flag) { /* Hack! */ 285 | printf(" %s", fullname); 286 | if (testcase->flags & TT_OFF_BY_DEFAULT) 287 | puts(" (Off by default)"); 288 | else if (testcase->flags & TT_SKIP) 289 | puts(" (DISABLED)"); 290 | else 291 | puts(""); 292 | } 293 | if (!strncmp(fullname, arg, length)) { 294 | if (set) 295 | testcase->flags |= flag; 296 | else 297 | testcase->flags &= ~flag; 298 | ++found; 299 | } 300 | } 301 | } 302 | return found; 303 | } 304 | 305 | static void usage(struct testgroup_t *groups, int list_groups) { 306 | puts("Options are: [--verbose|--quiet|--terse] [--no-fork]"); 307 | puts(" Specify tests by name, or using a prefix ending with '..'"); 308 | puts(" To skip a test, prefix its name with a colon."); 309 | puts(" To enable a disabled test, prefix its name with a plus."); 310 | puts(" Use --list-tests for a list of tests."); 311 | if (list_groups) { 312 | puts("Known tests are:"); 313 | tinytest_set_flag_(groups, "..", 1, 0); 314 | } 315 | exit(0); 316 | } 317 | 318 | static int process_test_alias(struct testgroup_t *groups, const char *test) { 319 | int i, j, n, r; 320 | for (i = 0; cfg_aliases && cfg_aliases[i].name; ++i) { 321 | if (!strcmp(cfg_aliases[i].name, test)) { 322 | n = 0; 323 | for (j = 0; cfg_aliases[i].tests[j]; ++j) { 324 | r = process_test_option(groups, cfg_aliases[i].tests[j]); 325 | if (r < 0) 326 | return -1; 327 | n += r; 328 | } 329 | return n; 330 | } 331 | } 332 | printf("No such test alias as @%s!", test); 333 | return -1; 334 | } 335 | 336 | static int process_test_option(struct testgroup_t *groups, const char *test) { 337 | int flag = TT_ENABLED_; 338 | int n = 0; 339 | if (test[0] == '@') { 340 | return process_test_alias(groups, test + 1); 341 | } else if (test[0] == ':') { 342 | ++test; 343 | flag = TT_SKIP; 344 | } else if (test[0] == '+') { 345 | ++test; 346 | ++n; 347 | if (!tinytest_set_flag_(groups, test, 0, TT_OFF_BY_DEFAULT)) { 348 | printf("No such test as %s!\n", test); 349 | return -1; 350 | } 351 | } else { 352 | ++n; 353 | } 354 | if (!tinytest_set_flag_(groups, test, 1, flag)) { 355 | printf("No such test as %s!\n", test); 356 | return -1; 357 | } 358 | return n; 359 | } 360 | 361 | void tinytest_set_aliases(const struct testlist_alias_t *aliases) { 362 | cfg_aliases = aliases; 363 | } 364 | 365 | int tinytest_main(int c, const char **v, struct testgroup_t *groups) { 366 | int i, j, n = 0; 367 | 368 | #ifdef _WIN32 369 | const char *sp = strrchr(v[0], '.'); 370 | const char *extension = ""; 371 | if (!sp || stricmp(sp, ".exe")) 372 | extension = ".exe"; /* Add an exe so CreateProcess will work */ 373 | snprintf(commandname, sizeof(commandname), "%s%s", v[0], extension); 374 | commandname[MAX_PATH] = '\0'; 375 | #endif 376 | for (i = 1; i < c; ++i) { 377 | if (v[i][0] == '-') { 378 | if (!strcmp(v[i], "--RUNNING-FORKED")) { 379 | opt_forked = 1; 380 | } else if (!strcmp(v[i], "--no-fork")) { 381 | opt_nofork = 1; 382 | } else if (!strcmp(v[i], "--quiet")) { 383 | opt_verbosity = -1; 384 | verbosity_flag = "--quiet"; 385 | } else if (!strcmp(v[i], "--verbose")) { 386 | opt_verbosity = 2; 387 | verbosity_flag = "--verbose"; 388 | } else if (!strcmp(v[i], "--terse")) { 389 | opt_verbosity = 0; 390 | verbosity_flag = "--terse"; 391 | } else if (!strcmp(v[i], "--help")) { 392 | usage(groups, 0); 393 | } else if (!strcmp(v[i], "--list-tests")) { 394 | usage(groups, 1); 395 | } else { 396 | printf("Unknown option %s. Try --help\n", v[i]); 397 | return -1; 398 | } 399 | } else { 400 | int r = process_test_option(groups, v[i]); 401 | if (r < 0) 402 | return -1; 403 | n += r; 404 | } 405 | } 406 | if (!n) 407 | tinytest_set_flag_(groups, "..", 1, TT_ENABLED_); 408 | 409 | #ifdef _IONBF 410 | setvbuf(stdout, NULL, _IONBF, 0); 411 | #endif 412 | 413 | ++in_tinytest_main; 414 | for (i = 0; groups[i].prefix; ++i) 415 | for (j = 0; groups[i].cases[j].name; ++j) 416 | if (groups[i].cases[j].flags & TT_ENABLED_) 417 | testcase_run_one(&groups[i], &groups[i].cases[j]); 418 | 419 | --in_tinytest_main; 420 | 421 | if (opt_verbosity == 0) 422 | puts(""); 423 | 424 | if (n_bad) 425 | printf("%d/%d TESTS FAILED. (%d skipped)\n", n_bad, n_bad + n_ok, 426 | n_skipped); 427 | else if (opt_verbosity >= 1) 428 | printf("%d tests ok. (%d skipped)\n", n_ok, n_skipped); 429 | 430 | return (n_bad == 0) ? 0 : 1; 431 | } 432 | 433 | int tinytest_get_verbosity_(void) { return opt_verbosity; } 434 | 435 | void tinytest_set_test_failed_(void) { 436 | if (opt_verbosity <= 0 && cur_test_name) { 437 | if (opt_verbosity == 0) 438 | puts(""); 439 | printf("%s%s: ", cur_test_prefix, cur_test_name); 440 | cur_test_name = NULL; 441 | } 442 | cur_test_outcome = FAIL; 443 | } 444 | 445 | void tinytest_set_test_skipped_(void) { 446 | if (cur_test_outcome == OK) 447 | cur_test_outcome = SKIP; 448 | } 449 | 450 | char *tinytest_format_hex_(const void *val_, unsigned long len) { 451 | const unsigned char *val = (unsigned char *)val_; 452 | char *result, *cp; 453 | size_t i; 454 | 455 | if (!val) 456 | return strdup("null"); 457 | if (!(result = (char *)malloc(len * 2 + 1))) 458 | return strdup(""); 459 | cp = result; 460 | for (i = 0; i < len; ++i) { 461 | *cp++ = "0123456789ABCDEF"[val[i] >> 4]; 462 | *cp++ = "0123456789ABCDEF"[val[i] & 0x0f]; 463 | } 464 | *cp = 0; 465 | return result; 466 | } 467 | -------------------------------------------------------------------------------- /src/ext/tinytest.h: -------------------------------------------------------------------------------- 1 | /* tinytest.h -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef TINYTEST_H_INCLUDED_ 27 | #define TINYTEST_H_INCLUDED_ 28 | 29 | /** Flag for a test that needs to run in a subprocess. */ 30 | #define TT_FORK (1 << 0) 31 | /** Runtime flag for a test we've decided to skip. */ 32 | #define TT_SKIP (1 << 1) 33 | /** Internal runtime flag for a test we've decided to run. */ 34 | #define TT_ENABLED_ (1 << 2) 35 | /** Flag for a test that's off by default. */ 36 | #define TT_OFF_BY_DEFAULT (1 << 3) 37 | /** If you add your own flags, make them start at this point. */ 38 | #define TT_FIRST_USER_FLAG (1 << 4) 39 | 40 | typedef void (*testcase_fn)(void *); 41 | 42 | struct testcase_t; 43 | 44 | /** Functions to initialize/teardown a structure for a testcase. */ 45 | struct testcase_setup_t { 46 | /** Return a new structure for use by a given testcase. */ 47 | void *(*setup_fn)(const struct testcase_t *); 48 | /** Clean/free a structure from setup_fn. Return 1 if ok, 0 on err. */ 49 | int (*cleanup_fn)(const struct testcase_t *, void *); 50 | }; 51 | 52 | /** A single test-case that you can run. */ 53 | struct testcase_t { 54 | const char *name; /**< An identifier for this case. */ 55 | testcase_fn fn; /**< The function to run to implement this case. */ 56 | unsigned long flags; /**< Bitfield of TT_* flags. */ 57 | const struct testcase_setup_t *setup; /**< Optional setup/cleanup fns*/ 58 | void *setup_data; /**< Extra data usable by setup function */ 59 | }; 60 | #define END_OF_TESTCASES \ 61 | { NULL, NULL, 0, NULL, NULL } 62 | 63 | /** A group of tests that are selectable together. */ 64 | struct testgroup_t { 65 | const char *prefix; /**< Prefix to prepend to testnames. */ 66 | struct testcase_t *cases; /** Array, ending with END_OF_TESTCASES */ 67 | }; 68 | #define END_OF_GROUPS \ 69 | { NULL, NULL } 70 | 71 | struct testlist_alias_t { 72 | const char *name; 73 | const char **tests; 74 | }; 75 | #define END_OF_ALIASES \ 76 | { NULL, NULL } 77 | 78 | /** Implementation: called from a test to indicate failure, before logging. */ 79 | void tinytest_set_test_failed_(void); 80 | /** Implementation: called from a test to indicate that we're skipping. */ 81 | void tinytest_set_test_skipped_(void); 82 | /** Implementation: return 0 for quiet, 1 for normal, 2 for loud. */ 83 | int tinytest_get_verbosity_(void); 84 | /** Implementation: Set a flag on tests matching a name; returns number 85 | * of tests that matched. */ 86 | int tinytest_set_flag_(struct testgroup_t *, const char *, int set, 87 | unsigned long); 88 | /** Implementation: Put a chunk of memory into hex. */ 89 | char *tinytest_format_hex_(const void *, unsigned long); 90 | 91 | /** Set all tests in 'groups' matching the name 'named' to be skipped. */ 92 | #define tinytest_skip(groups, named) \ 93 | tinytest_set_flag_(groups, named, 1, TT_SKIP) 94 | 95 | /** Run a single testcase in a single group. */ 96 | int testcase_run_one(const struct testgroup_t *, const struct testcase_t *); 97 | 98 | void tinytest_set_aliases(const struct testlist_alias_t *aliases); 99 | 100 | /** Run a set of testcases from an END_OF_GROUPS-terminated array of groups, 101 | as selected from the command line. */ 102 | int tinytest_main(int argc, const char **argv, struct testgroup_t *groups); 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /src/ext/tinytest_macros.h: -------------------------------------------------------------------------------- 1 | /* tinytest_macros.h -- Copyright 2009-2012 Nick Mathewson 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * 1. Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * 3. The name of the author may not be used to endorse or promote products 12 | * derived from this software without specific prior written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef TINYTEST_MACROS_H_INCLUDED_ 27 | #define TINYTEST_MACROS_H_INCLUDED_ 28 | 29 | /* Helpers for defining statement-like macros */ 30 | #define TT_STMT_BEGIN do { 31 | #define TT_STMT_END \ 32 | } \ 33 | while (0) 34 | 35 | /* Redefine this if your test functions want to abort with something besides 36 | * "goto end;" */ 37 | #ifndef TT_EXIT_TEST_FUNCTION 38 | #define TT_EXIT_TEST_FUNCTION \ 39 | TT_STMT_BEGIN goto end; \ 40 | TT_STMT_END 41 | #endif 42 | 43 | /* Redefine this if you want to note success/failure in some different way. */ 44 | #ifndef TT_DECLARE 45 | #define TT_DECLARE(prefix, args) \ 46 | TT_STMT_BEGIN \ 47 | printf("\n %s %s:%d: ", prefix, __FILE__, __LINE__); \ 48 | printf args; \ 49 | TT_STMT_END 50 | #endif 51 | 52 | /* Announce a failure. Args are parenthesized printf args. */ 53 | #define TT_GRIPE(args) TT_DECLARE("FAIL", args) 54 | 55 | /* Announce a non-failure if we're verbose. */ 56 | #define TT_BLATHER(args) \ 57 | TT_STMT_BEGIN \ 58 | if (tinytest_get_verbosity_() > 1) \ 59 | TT_DECLARE(" OK", args); \ 60 | TT_STMT_END 61 | 62 | #define TT_DIE(args) \ 63 | TT_STMT_BEGIN \ 64 | tinytest_set_test_failed_(); \ 65 | TT_GRIPE(args); \ 66 | TT_EXIT_TEST_FUNCTION; \ 67 | TT_STMT_END 68 | 69 | #define TT_FAIL(args) \ 70 | TT_STMT_BEGIN \ 71 | tinytest_set_test_failed_(); \ 72 | TT_GRIPE(args); \ 73 | TT_STMT_END 74 | 75 | /* Fail and abort the current test for the reason in msg */ 76 | #define tt_abort_printf(msg) TT_DIE(msg) 77 | #define tt_abort_perror(op) \ 78 | TT_DIE(("%s: %s [%d]", (op), strerror(errno), errno)) 79 | #define tt_abort_msg(msg) TT_DIE(("%s", msg)) 80 | #define tt_abort() TT_DIE(("%s", "(Failed.)")) 81 | 82 | /* Fail but do not abort the current test for the reason in msg. */ 83 | #define tt_fail_printf(msg) TT_FAIL(msg) 84 | #define tt_fail_perror(op) \ 85 | TT_FAIL(("%s: %s [%d]", (op), strerror(errno), errno)) 86 | #define tt_fail_msg(msg) TT_FAIL(("%s", msg)) 87 | #define tt_fail() TT_FAIL(("%s", "(Failed.)")) 88 | 89 | /* End the current test, and indicate we are skipping it. */ 90 | #define tt_skip() \ 91 | TT_STMT_BEGIN \ 92 | tinytest_set_test_skipped_(); \ 93 | TT_EXIT_TEST_FUNCTION; \ 94 | TT_STMT_END 95 | 96 | #define tt_want_(b, msg, fail) \ 97 | TT_STMT_BEGIN \ 98 | if (!(b)) { \ 99 | tinytest_set_test_failed_(); \ 100 | TT_GRIPE(("%s", msg)); \ 101 | fail; \ 102 | } else { \ 103 | TT_BLATHER(("%s", msg)); \ 104 | } \ 105 | TT_STMT_END 106 | 107 | /* Assert b, but do not stop the test if b fails. Log msg on failure. */ 108 | #define tt_want_msg(b, msg) tt_want_(b, msg, ); 109 | 110 | /* Assert b and stop the test if b fails. Log msg on failure. */ 111 | #define tt_assert_msg(b, msg) tt_want_(b, msg, TT_EXIT_TEST_FUNCTION); 112 | 113 | /* Assert b, but do not stop the test if b fails. */ 114 | #define tt_want(b) tt_want_msg((b), "want(" #b ")") 115 | /* Assert b, and stop the test if b fails. */ 116 | #define tt_assert(b) tt_assert_msg((b), "assert(" #b ")") 117 | 118 | #define tt_assert_test_fmt_type(a, b, str_test, type, test, printf_type, \ 119 | printf_fmt, setup_block, cleanup_block, \ 120 | die_on_fail) \ 121 | TT_STMT_BEGIN \ 122 | type val1_ = (a); \ 123 | type val2_ = (b); \ 124 | int tt_status_ = (test); \ 125 | if (!tt_status_ || tinytest_get_verbosity_() > 1) { \ 126 | printf_type print_; \ 127 | printf_type print1_; \ 128 | printf_type print2_; \ 129 | type value_ = val1_; \ 130 | setup_block; \ 131 | print1_ = print_; \ 132 | value_ = val2_; \ 133 | setup_block; \ 134 | print2_ = print_; \ 135 | TT_DECLARE(tt_status_ ? " OK" : "FAIL", \ 136 | ("assert(%s): " printf_fmt " vs " printf_fmt, str_test, \ 137 | print1_, print2_)); \ 138 | print_ = print1_; \ 139 | cleanup_block; \ 140 | print_ = print2_; \ 141 | cleanup_block; \ 142 | if (!tt_status_) { \ 143 | tinytest_set_test_failed_(); \ 144 | die_on_fail; \ 145 | } \ 146 | } \ 147 | TT_STMT_END 148 | 149 | #define tt_assert_test_type(a, b, str_test, type, test, fmt, die_on_fail) \ 150 | tt_assert_test_fmt_type(a, b, str_test, type, test, type, fmt, \ 151 | { print_ = value_; }, {}, die_on_fail) 152 | 153 | #define tt_assert_test_type_opt(a, b, str_test, type, test, fmt, die_on_fail) \ 154 | tt_assert_test_fmt_type(a, b, str_test, type, test, type, fmt, \ 155 | { print_ = value_ ? value_ : ""; }, {}, \ 156 | die_on_fail) 157 | 158 | /* Helper: assert that a op b, when cast to type. Format the values with 159 | * printf format fmt on failure. */ 160 | #define tt_assert_op_type(a, op, b, type, fmt) \ 161 | tt_assert_test_type(a, b, #a " " #op " " #b, type, (val1_ op val2_), fmt, \ 162 | TT_EXIT_TEST_FUNCTION) 163 | 164 | #define tt_int_op(a, op, b) \ 165 | tt_assert_test_type(a, b, #a " " #op " " #b, long, (val1_ op val2_), "%ld", \ 166 | TT_EXIT_TEST_FUNCTION) 167 | 168 | #define tt_uint_op(a, op, b) \ 169 | tt_assert_test_type(a, b, #a " " #op " " #b, unsigned long, \ 170 | (val1_ op val2_), "%lu", TT_EXIT_TEST_FUNCTION) 171 | 172 | #define tt_ptr_op(a, op, b) \ 173 | tt_assert_test_type(a, b, #a " " #op " " #b, const void *, (val1_ op val2_), \ 174 | "%p", TT_EXIT_TEST_FUNCTION) 175 | 176 | #define tt_str_op(a, op, b) \ 177 | tt_assert_test_type_opt(a, b, #a " " #op " " #b, const char *, \ 178 | (val1_ && val2_ && strcmp(val1_, val2_) op 0), \ 179 | "<%s>", TT_EXIT_TEST_FUNCTION) 180 | 181 | #define tt_mem_op(expr1, op, expr2, len) \ 182 | tt_assert_test_fmt_type( \ 183 | expr1, expr2, #expr1 " " #op " " #expr2, const void *, \ 184 | (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), char *, "%s", \ 185 | { print_ = tinytest_format_hex_(value_, (len)); }, \ 186 | { \ 187 | if (print_) \ 188 | free(print_); \ 189 | }, \ 190 | TT_EXIT_TEST_FUNCTION); 191 | 192 | #define tt_want_int_op(a, op, b) \ 193 | tt_assert_test_type(a, b, #a " " #op " " #b, long, (val1_ op val2_), "%ld", \ 194 | (void)0) 195 | 196 | #define tt_want_uint_op(a, op, b) \ 197 | tt_assert_test_type(a, b, #a " " #op " " #b, unsigned long, \ 198 | (val1_ op val2_), "%lu", (void)0) 199 | 200 | #define tt_want_ptr_op(a, op, b) \ 201 | tt_assert_test_type(a, b, #a " " #op " " #b, const void *, (val1_ op val2_), \ 202 | "%p", (void)0) 203 | 204 | #define tt_want_str_op(a, op, b) \ 205 | tt_assert_test_type(a, b, #a " " #op " " #b, const char *, \ 206 | (strcmp(val1_, val2_) op 0), "<%s>", (void)0) 207 | 208 | #define tt_want_mem_op(expr1, op, expr2, len) \ 209 | tt_assert_test_fmt_type( \ 210 | expr1, expr2, #expr1 " " #op " " #expr2, const void *, \ 211 | (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), char *, "%s", \ 212 | { print_ = tinytest_format_hex_(value_, (len)); }, \ 213 | { \ 214 | if (print_) \ 215 | free(print_); \ 216 | }, \ 217 | (void)0); 218 | 219 | #endif 220 | -------------------------------------------------------------------------------- /src/libsam3/libsam3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include "libsam3.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #ifdef __MINGW32__ 39 | //#include 40 | #include 41 | #include 42 | #include 43 | #ifndef MSG_NOSIGNAL 44 | #define MSG_NOSIGNAL 0 45 | #endif 46 | #ifndef SHUT_RDWR 47 | #define SHUT_RDWR 2 48 | #endif 49 | #endif 50 | 51 | #if defined(__unix__) || defined(__APPLE__) 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #endif 59 | 60 | #if defined(__unix__) && !defined(__APPLE__) 61 | #include 62 | #endif 63 | 64 | #if defined(__APPLE__) 65 | #ifndef MSG_NOSIGNAL 66 | #define MSG_NOSIGNAL 0 67 | #endif 68 | #include 69 | uint32_t TickCount() { 70 | uint64_t mat = mach_absolute_time(); 71 | uint32_t mul = 0x80d9594e; 72 | return ((((0xffffffff & mat) * mul) >> 32) + (mat >> 32) * mul) >> 23; 73 | } 74 | #endif 75 | 76 | //////////////////////////////////////////////////////////////////////////////// 77 | int libsam3_debug = 0; 78 | 79 | //////////////////////////////////////////////////////////////////////////////// 80 | /* convert struct timeval to milliseconds */ 81 | /* 82 | static inline uint64_t timeval2ms (const struct timeval *tv) { 83 | return ((uint64_t)tv->tv_sec)*1000+((uint64_t)tv->tv_usec)/1000; 84 | } 85 | */ 86 | 87 | /* convert milliseconds to timeval struct */ 88 | static inline void ms2timeval(struct timeval *tv, uint64_t ms) { 89 | tv->tv_sec = ms / 1000; 90 | tv->tv_usec = (ms % 1000) * 1000; 91 | } 92 | 93 | int sam3tcpSetTimeoutSend(int fd, int timeoutms) { 94 | if (fd >= 0 && timeoutms >= 0) { 95 | struct timeval tv; 96 | // 97 | ms2timeval(&tv, timeoutms); 98 | return (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0 ? -1 99 | : 0); 100 | } 101 | return -1; 102 | } 103 | 104 | int sam3tcpSetTimeoutReceive(int fd, int timeoutms) { 105 | if (fd >= 0 && timeoutms >= 0) { 106 | struct timeval tv; 107 | // 108 | ms2timeval(&tv, timeoutms); 109 | return (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ? -1 110 | : 0); 111 | } 112 | return -1; 113 | } 114 | 115 | int sam3CheckValidKeyLength(const char *pubkey) { 116 | if (strlen(pubkey) >= SAM3_PUBKEY_SIZE && 117 | strlen(pubkey) <= SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE) { 118 | return 1; 119 | } 120 | return 0; 121 | } 122 | 123 | int sam3tcpConnectIP(uint32_t ip, int port) { 124 | struct sockaddr_in addr; 125 | int fd, val = 1; 126 | char ipstr[18]; 127 | // 128 | if (ip == 0 || ip == 0xffffffffUL || port < 1 || port > 65535) 129 | return -1; 130 | // 131 | if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 132 | if (libsam3_debug) 133 | fprintf(stderr, "ERROR: can't create socket\n"); 134 | return -1; 135 | } 136 | // 137 | memset(&addr, 0, sizeof(addr)); 138 | addr.sin_family = AF_INET; 139 | addr.sin_port = htons(port); 140 | addr.sin_addr.s_addr = ip; 141 | // 142 | ipstr[0] = 0; 143 | if (libsam3_debug) { 144 | if (getnameinfo((struct sockaddr *)&addr, sizeof(struct sockaddr_in), ipstr, 145 | sizeof(ipstr), NULL, 0, NI_NUMERICHOST) == 0) { 146 | fprintf(stderr, "connecting to [%s:%d]...\n", ipstr, port); 147 | } 148 | } 149 | // 150 | setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); 151 | // 152 | if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { 153 | if (libsam3_debug) 154 | fprintf(stderr, "ERROR: can't connect\n"); 155 | close(fd); 156 | return -1; 157 | } 158 | // 159 | if (libsam3_debug && ipstr[0]) 160 | fprintf(stderr, "connected to [%s:%d]\n", ipstr, port); 161 | // 162 | return fd; 163 | } 164 | 165 | /* returns fd or -1 */ 166 | int sam3tcpConnect(const char *hostname, int port, uint32_t *ip) { 167 | struct hostent *host = NULL; 168 | // 169 | if (hostname == NULL || !hostname[0] || port < 1 || port > 65535) 170 | return -1; 171 | // 172 | host = gethostbyname(hostname); 173 | if (host == NULL || host->h_name == NULL || !host->h_name[0]) { 174 | if (libsam3_debug) 175 | fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); 176 | return -1; 177 | } 178 | // 179 | if (libsam3_debug) { 180 | char ipstr[18]; 181 | // 182 | struct sockaddr_in addr; 183 | memset(&addr, 0, sizeof(addr)); 184 | addr.sin_family = AF_INET; 185 | addr.sin_port = htons(port); 186 | addr.sin_addr = *((struct in_addr *)host->h_addr); 187 | // 188 | if (getnameinfo((struct sockaddr *)&addr, sizeof(struct sockaddr_in), ipstr, 189 | sizeof(ipstr), NULL, 0, NI_NUMERICHOST) == 0) { 190 | fprintf(stderr, "resolving: %s is [%s]...\n", hostname, ipstr); 191 | } 192 | } 193 | // 194 | if (ip != NULL) 195 | *ip = ((struct in_addr *)host->h_addr)->s_addr; 196 | return sam3tcpConnectIP(((struct in_addr *)host->h_addr)->s_addr, port); 197 | } 198 | 199 | // <0: error; 0: ok 200 | int sam3tcpDisconnect(int fd) { 201 | if (fd >= 0) { 202 | shutdown(fd, SHUT_RDWR); 203 | return close(fd); 204 | } 205 | // 206 | return -1; 207 | } 208 | 209 | //////////////////////////////////////////////////////////////////////////////// 210 | // <0: error; 0: ok 211 | int sam3tcpSend(int fd, const void *buf, size_t bufSize) { 212 | const char *c = (const char *)buf; 213 | // 214 | if (fd < 0 || (buf == NULL && bufSize > 0)) 215 | return -1; 216 | // 217 | while (bufSize > 0) { 218 | int wr = send(fd, c, bufSize, MSG_NOSIGNAL); 219 | // 220 | if (wr < 0 && errno == EINTR) 221 | continue; // interrupted by signal 222 | if (wr <= 0) 223 | return -1; // either error or 224 | c += wr; 225 | bufSize -= wr; 226 | } 227 | // 228 | return 0; 229 | } 230 | 231 | /* <0: received (-res) bytes; read error */ 232 | /* can return less that requesten bytes even if `allowPartial` is 0 when 233 | * connection is closed */ 234 | ssize_t sam3tcpReceiveEx(int fd, void *buf, size_t bufSize, int allowPartial) { 235 | char *c = (char *)buf; 236 | ssize_t total = 0; 237 | // 238 | if (fd < 0 || (buf == NULL && bufSize > 0)) 239 | return -1; 240 | // 241 | while (bufSize > 0) { 242 | int rd = recv(fd, c, bufSize, 0); 243 | // 244 | if (rd < 0 && errno == EINTR) 245 | continue; // interrupted by signal 246 | if (rd == 0) 247 | return total; 248 | if (rd < 0) 249 | return -total; 250 | c += rd; 251 | total += rd; 252 | bufSize -= rd; 253 | if (allowPartial) 254 | break; 255 | } 256 | // 257 | return total; 258 | } 259 | 260 | ssize_t sam3tcpReceive(int fd, void *buf, size_t bufSize) { 261 | return sam3tcpReceiveEx(fd, buf, bufSize, 0); 262 | } 263 | 264 | //////////////////////////////////////////////////////////////////////////////// 265 | __attribute__((format(printf, 2, 3))) int sam3tcpPrintf(int fd, const char *fmt, 266 | ...) { 267 | int res; 268 | char buf[1024], *p = buf; 269 | int size = sizeof(buf) - 1; 270 | // 271 | for (;;) { 272 | va_list ap; 273 | char *np; 274 | int n; 275 | // 276 | va_start(ap, fmt); 277 | n = vsnprintf(p, size, fmt, ap); 278 | va_end(ap); 279 | // 280 | if (n > -1 && n < size) 281 | break; 282 | if (n > -1) 283 | size = n + 1; 284 | else 285 | size *= 2; 286 | if (p == buf) { 287 | if ((p = malloc(size + 4)) == NULL) 288 | return -1; 289 | } else { 290 | if ((np = realloc(p, size + 4)) == NULL) { 291 | free(p); 292 | return -1; 293 | } 294 | p = np; 295 | } 296 | } 297 | // 298 | if (libsam3_debug) 299 | fprintf(stderr, "SENDING: %s", p); 300 | res = sam3tcpSend(fd, p, strlen(p)); 301 | if (p != buf) 302 | free(p); 303 | return res; 304 | } 305 | 306 | int sam3tcpReceiveStr(int fd, char *dest, size_t maxSize) { 307 | char *d = dest; 308 | // 309 | if (maxSize < 1 || fd < 0 || dest == NULL) 310 | return -1; 311 | memset(dest, 0, maxSize); 312 | while (maxSize > 1) { 313 | char *e; 314 | int rd = recv(fd, d, maxSize - 1, MSG_PEEK); 315 | // 316 | if (rd < 0 && errno == EINTR) 317 | continue; // interrupted by signal 318 | if (rd == 0) { 319 | rd = recv(fd, d, 1, 0); 320 | if (rd < 0 && errno == EINTR) 321 | continue; // interrupted by signal 322 | if (d[0] == '\n') { 323 | d[0] = 0; // remove '\n' 324 | return 0; 325 | } 326 | } else { 327 | if (rd < 0) 328 | return -1; // error or connection closed; alas 329 | } 330 | // check for EOL 331 | d[maxSize - 1] = 0; 332 | if ((e = strchr(d, '\n')) != NULL) { 333 | rd = e - d + 1; // bytes to receive 334 | if (sam3tcpReceive(fd, d, rd) < 0) 335 | return -1; // alas 336 | d[rd - 1] = 0; // remove '\n' 337 | return 0; // done 338 | } else { 339 | // let's receive this part and go on 340 | if (sam3tcpReceive(fd, d, rd) < 0) 341 | return -1; // alas 342 | maxSize -= rd; 343 | d += rd; 344 | } 345 | } 346 | // alas, the string is too big 347 | return -1; 348 | } 349 | 350 | //////////////////////////////////////////////////////////////////////////////// 351 | int sam3udpSendToIP(uint32_t ip, int port, const void *buf, size_t bufSize) { 352 | // TODO: ipv6 353 | struct sockaddr_in addr; 354 | int fd, res; 355 | // 356 | if (buf == NULL || bufSize < 1) 357 | return -1; 358 | if (port < 1 || port > 65535) 359 | port = 7655; 360 | // 361 | if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { 362 | if (libsam3_debug) 363 | fprintf(stderr, "ERROR: can't create socket\n"); 364 | return -1; 365 | } 366 | // 367 | memset(&addr, 0, sizeof(addr)); 368 | addr.sin_family = AF_INET; 369 | addr.sin_port = htons(port); 370 | addr.sin_addr.s_addr = ip; 371 | // 372 | res = sendto(fd, buf, bufSize, 0, (struct sockaddr *)&addr, sizeof(addr)); 373 | // 374 | if (res < 0) { 375 | if (libsam3_debug) { 376 | res = errno; 377 | fprintf(stderr, "UDP ERROR (%d): %s\n", res, strerror(res)); 378 | } 379 | res = -1; 380 | } else { 381 | if (libsam3_debug) 382 | fprintf(stderr, "UDP: %d bytes sent\n", res); 383 | } 384 | // 385 | close(fd); 386 | // 387 | return (res >= 0 ? 0 : -1); 388 | } 389 | 390 | int sam3udpSendTo(const char *hostname, int port, const void *buf, 391 | size_t bufSize, uint32_t *ip) { 392 | struct hostent *host = NULL; 393 | // TODO: ipv6 394 | if (buf == NULL || bufSize < 1) 395 | return -1; 396 | if (hostname == NULL || !hostname[0]) 397 | hostname = "localhost"; 398 | if (port < 1 || port > 65535) 399 | port = 7655; 400 | // 401 | host = gethostbyname(hostname); 402 | if (host == NULL || host->h_name == NULL || !host->h_name[0]) { 403 | if (libsam3_debug) 404 | fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); 405 | return -1; 406 | } 407 | // 408 | if (ip != NULL) 409 | *ip = ((struct in_addr *)host->h_addr)->s_addr; 410 | return sam3udpSendToIP(((struct in_addr *)host->h_addr)->s_addr, port, buf, 411 | bufSize); 412 | } 413 | 414 | //////////////////////////////////////////////////////////////////////////////// 415 | void sam3FreeFieldList(SAMFieldList *list) { 416 | while (list != NULL) { 417 | SAMFieldList *c = list; 418 | // 419 | list = list->next; 420 | if (c->name != NULL) 421 | free(c->name); 422 | if (c->value != NULL) 423 | free(c->value); 424 | free(c); 425 | } 426 | } 427 | 428 | void sam3DumpFieldList(const SAMFieldList *list) { 429 | for (; list != NULL; list = list->next) { 430 | fprintf(stderr, "%s=[%s]\n", list->name, list->value); 431 | } 432 | } 433 | 434 | const char *sam3FindField(const SAMFieldList *list, const char *field) { 435 | if (list != NULL && field != NULL) { 436 | for (list = list->next; list != NULL; list = list->next) { 437 | if (list->name != NULL && strcmp(field, list->name) == 0) 438 | return list->value; 439 | } 440 | } 441 | return NULL; 442 | } 443 | 444 | static char *xstrdup(const char *s, int len) { 445 | if (len >= 0) { 446 | char *res = malloc(len + 1); 447 | // 448 | if (res != NULL) { 449 | if (len > 0) 450 | memcpy(res, s, len); 451 | res[len] = 0; 452 | } 453 | // 454 | return res; 455 | } 456 | // 457 | return NULL; 458 | } 459 | 460 | // returns NULL if there are no more tokens 461 | static inline const char *xstrtokend(const char *s) { 462 | while (*s && isspace(*s)) 463 | ++s; 464 | // 465 | if (*s) { 466 | char qch = 0; 467 | // 468 | while (*s) { 469 | if (*s == qch) { 470 | qch = 0; 471 | } else if (*s == '"') { 472 | qch = *s; 473 | } else if (qch) { 474 | if (*s == '\\' && s[1]) 475 | ++s; 476 | } else if (isspace(*s)) { 477 | break; 478 | } 479 | ++s; 480 | } 481 | } else { 482 | s = NULL; 483 | } 484 | // 485 | return s; 486 | } 487 | 488 | SAMFieldList *sam3ParseReply(const char *rep) { 489 | SAMFieldList *first = NULL, *last, *c; 490 | const char *p = rep, *e, *e1; 491 | // 492 | // first 2 words 493 | while (*p && isspace(*p)) 494 | ++p; 495 | if ((e = xstrtokend(p)) == NULL) 496 | return NULL; 497 | if ((e1 = xstrtokend(e)) == NULL) 498 | return NULL; 499 | // 500 | if ((first = last = c = malloc(sizeof(SAMFieldList))) == NULL) 501 | return NULL; 502 | c->next = NULL; 503 | c->name = c->value = NULL; 504 | if ((c->name = xstrdup(p, e - p)) == NULL) 505 | goto error; 506 | while (*e && isspace(*e)) 507 | ++e; 508 | if ((c->value = xstrdup(e, e1 - e)) == NULL) 509 | goto error; 510 | // 511 | p = e1; 512 | while (*p) { 513 | while (*p && isspace(*p)) 514 | ++p; 515 | if ((e = xstrtokend(p)) == NULL) 516 | break; // no more tokens 517 | // 518 | if (libsam3_debug) 519 | fprintf(stderr, "<%s>\n", p); 520 | // 521 | if ((c = malloc(sizeof(SAMFieldList))) == NULL) 522 | return NULL; 523 | c->next = NULL; 524 | c->name = c->value = NULL; 525 | last->next = c; 526 | last = c; 527 | // 528 | if ((e1 = memchr(p, '=', e - p)) != NULL) { 529 | // key=value 530 | if ((c->name = xstrdup(p, e1 - p)) == NULL) 531 | goto error; 532 | if ((c->value = xstrdup(e1 + 1, e - e1 - 1)) == NULL) 533 | goto error; 534 | } else { 535 | // only key (there is no such replies in SAMv3, but... 536 | if ((c->name = xstrdup(p, e - p)) == NULL) 537 | goto error; 538 | if ((c->value = strdup("")) == NULL) 539 | goto error; 540 | } 541 | p = e; 542 | } 543 | // 544 | if (libsam3_debug) 545 | sam3DumpFieldList(first); 546 | // 547 | return first; 548 | error: 549 | sam3FreeFieldList(first); 550 | return NULL; 551 | } 552 | 553 | // NULL: error; else: list of fields 554 | // first item is always 2-word reply, with first word in name and second in 555 | // value 556 | SAMFieldList *sam3ReadReply(int fd) { 557 | char rep[2048]; // should be enough for any reply 558 | // 559 | if (sam3tcpReceiveStr(fd, rep, sizeof(rep)) < 0) 560 | return NULL; 561 | if (libsam3_debug) 562 | fprintf(stderr, "SAM REPLY: [%s]\n", rep); 563 | return sam3ParseReply(rep); 564 | } 565 | 566 | // example: 567 | // r0: 'HELLO' 568 | // r1: 'REPLY' 569 | // field: NULL or 'RESULT' 570 | // VALUE: NULL or 'OK' 571 | // returns bool 572 | int sam3IsGoodReply(const SAMFieldList *list, const char *r0, const char *r1, 573 | const char *field, const char *value) { 574 | if (list != NULL && list->name != NULL && list->value != NULL) { 575 | if (r0 != NULL && strcmp(r0, list->name) != 0) 576 | return 0; 577 | if (r1 != NULL && strcmp(r1, list->value) != 0) 578 | return 0; 579 | if (field != NULL) { 580 | for (list = list->next; list != NULL; list = list->next) { 581 | if (list->name == NULL || list->value == NULL) 582 | return 0; // invalid list, heh 583 | if (strcmp(field, list->name) == 0) { 584 | if (value != NULL && strcmp(value, list->value) != 0) 585 | return 0; 586 | return 1; 587 | } 588 | } 589 | } 590 | return 1; 591 | } 592 | return 0; 593 | } 594 | 595 | //////////////////////////////////////////////////////////////////////////////// 596 | // by Bob Jenkins 597 | // public domain 598 | // http://burtleburtle.net/bob/rand/smallprng.html 599 | // 600 | //////////////////////////////////////////////////////////////////////////////// 601 | typedef struct { 602 | uint32_t a, b, c, d; 603 | } BJRandCtx; 604 | 605 | #define BJPRNG_ROT(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) 606 | 607 | static uint32_t bjprngRand(BJRandCtx *x) { 608 | uint32_t e; 609 | /* original: 610 | e = x->a-BJPRNG_ROT(x->b, 27); 611 | x->a = x->b^BJPRNG_ROT(x->c, 17); 612 | x->b = x->c+x->d; 613 | x->c = x->d+e; 614 | x->d = e+x->a; 615 | */ 616 | /* better, but slower at least in idiotic m$vc */ 617 | e = x->a - BJPRNG_ROT(x->b, 23); 618 | x->a = x->b ^ BJPRNG_ROT(x->c, 16); 619 | x->b = x->c + BJPRNG_ROT(x->d, 11); 620 | x->c = x->d + e; 621 | x->d = e + x->a; 622 | // 623 | return x->d; 624 | } 625 | 626 | static void bjprngInit(BJRandCtx *x, uint32_t seed) { 627 | x->a = 0xf1ea5eed; 628 | x->b = x->c = x->d = seed; 629 | for (int i = 0; i < 20; ++i) 630 | bjprngRand(x); 631 | } 632 | 633 | static inline uint32_t hashint(uint32_t a) { 634 | a -= (a << 6); 635 | a ^= (a >> 17); 636 | a -= (a << 9); 637 | a ^= (a << 4); 638 | a -= (a << 3); 639 | a ^= (a << 10); 640 | a ^= (a >> 15); 641 | return a; 642 | } 643 | 644 | static uint32_t genSeed(void) { 645 | volatile uint32_t seed = 1; 646 | uint32_t res; 647 | #ifndef WIN32 648 | #ifndef __APPLE__ 649 | struct sysinfo sy; 650 | pid_t pid = getpid(); 651 | // 652 | sysinfo(&sy); 653 | res = hashint((uint32_t)pid) ^ hashint((uint32_t)time(NULL)) ^ 654 | hashint((uint32_t)sy.sharedram) ^ hashint((uint32_t)sy.bufferram) ^ 655 | hashint((uint32_t)sy.uptime); 656 | #else 657 | res = hashint((uint32_t)getpid()) ^ 658 | hashint((uint32_t)TickCount()); 659 | #endif 660 | #else 661 | res = hashint((uint32_t)GetCurrentProcessId()) ^ 662 | hashint((uint32_t)GetTickCount()); 663 | #endif 664 | res += __sync_fetch_and_add(&seed, 1); 665 | // 666 | return hashint(res); 667 | } 668 | 669 | //////////////////////////////////////////////////////////////////////////////// 670 | size_t sam3GenChannelName(char *dest, size_t minlen, size_t maxlen) { 671 | BJRandCtx rc; 672 | size_t len; 673 | size_t retlen; 674 | // 675 | if (dest == NULL || minlen < 1 || maxlen < minlen || minlen > 65536 || 676 | maxlen > 65536) 677 | return -1; 678 | bjprngInit(&rc, genSeed()); 679 | len = minlen + (bjprngRand(&rc) % (maxlen - minlen + 1)); 680 | retlen = len; 681 | while (len--) { 682 | int ch = bjprngRand(&rc) % 64; 683 | // 684 | if (ch >= 0 && ch < 10) 685 | ch += '0'; 686 | else if (ch >= 10 && ch < 36) 687 | ch += 'A' - 10; 688 | else if (ch >= 36 && ch < 62) 689 | ch += 'a' - 36; 690 | else if (ch == 62) 691 | ch = '-'; 692 | else if (ch == 63) 693 | ch = '_'; 694 | else if (ch > 64) 695 | abort(); 696 | *dest++ = ch; 697 | } 698 | *dest++ = 0; 699 | return retlen; 700 | } 701 | 702 | //////////////////////////////////////////////////////////////////////////////// 703 | static int sam3HandshakeInternal(int fd) { 704 | SAMFieldList *rep = NULL; 705 | // 706 | if (sam3tcpPrintf(fd, "HELLO VERSION MIN=3.0 MAX=3.1\n") < 0) 707 | goto error; 708 | rep = sam3ReadReply(fd); 709 | if (!sam3IsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK")) 710 | goto error; 711 | sam3FreeFieldList(rep); 712 | return fd; 713 | error: 714 | sam3tcpDisconnect(fd); 715 | if (rep != NULL) 716 | sam3FreeFieldList(rep); 717 | return -1; 718 | } 719 | 720 | int sam3HandshakeIP(uint32_t ip, int port) { 721 | int fd; 722 | // 723 | if ((fd = sam3tcpConnectIP(ip, (port < 1 || port > 65535 ? 7656 : port))) < 0) 724 | return -1; 725 | return sam3HandshakeInternal(fd); 726 | } 727 | 728 | int sam3Handshake(const char *hostname, int port, uint32_t *ip) { 729 | int fd; 730 | // 731 | if ((fd = sam3tcpConnect( 732 | (hostname == NULL || !hostname[0] ? "localhost" : hostname), 733 | (port < 1 || port > 65535 ? 7656 : port), ip)) < 0) 734 | return -1; 735 | return sam3HandshakeInternal(fd); 736 | } 737 | 738 | //////////////////////////////////////////////////////////////////////////////// 739 | static inline void strcpyerr(Sam3Session *ses, const char *errstr) { 740 | memset(ses->error, 0, sizeof(ses->error)); 741 | if (errstr != NULL) 742 | strncpy(ses->error, errstr, sizeof(ses->error) - 1); 743 | } 744 | 745 | int sam3GenerateKeys(Sam3Session *ses, const char *hostname, int port, 746 | int sigType) { 747 | if (ses != NULL) { 748 | SAMFieldList *rep = NULL; 749 | int fd, res = -1; 750 | static const char *sigtypes[5] = { 751 | "SIGNATURE_TYPE=DSA_SHA1", "SIGNATURE_TYPE=ECDSA_SHA256_P256", 752 | "SIGNATURE_TYPE=ECDSA_SHA384_P384", "SIGNATURE_TYPE=ECDSA_SHA512_P521", 753 | "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"}; 754 | // 755 | if ((fd = sam3Handshake(hostname, port, NULL)) < 0) { 756 | strcpyerr(ses, "I2P_ERROR"); 757 | return -1; 758 | } 759 | // 760 | if (sam3tcpPrintf(fd, "DEST GENERATE %s\n", sigtypes[(int)sigType]) < 0) { 761 | strcpyerr(ses, "DEST_ERROR"); 762 | } 763 | 764 | rep = sam3ReadReply(fd); 765 | // sam3DumpFieldList(rep); 766 | if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PUB", NULL)) { 767 | strcpyerr(ses, "PUBKEY_ERROR"); 768 | } 769 | if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PRIV", NULL)) { 770 | strcpyerr(ses, "PRIVKEY_ERROR"); 771 | } 772 | const char *pub = sam3FindField(rep, "PUB"); 773 | strcpy(ses->pubkey, pub); 774 | const char *priv = sam3FindField(rep, "PRIV"); 775 | strcpy(ses->privkey, priv); 776 | res = 0; 777 | // 778 | sam3FreeFieldList(rep); 779 | sam3tcpDisconnect(fd); 780 | // 781 | return res; 782 | } 783 | return -1; 784 | } 785 | 786 | int sam3NameLookup(Sam3Session *ses, const char *hostname, int port, 787 | const char *name) { 788 | if (ses != NULL && name != NULL && name[0]) { 789 | SAMFieldList *rep = NULL; 790 | int fd, res = -1; 791 | // 792 | if ((fd = sam3Handshake(hostname, port, NULL)) < 0) { 793 | strcpyerr(ses, "I2P_ERROR"); 794 | return -1; 795 | } 796 | // 797 | strcpyerr(ses, "I2P_ERROR"); 798 | if (sam3tcpPrintf(fd, "NAMING LOOKUP NAME=%s\n", name) >= 0) { 799 | if ((rep = sam3ReadReply(fd)) != NULL && 800 | sam3IsGoodReply(rep, "NAMING", "REPLY", "RESULT", NULL)) { 801 | const char *rs = sam3FindField(rep, "RESULT"), 802 | *pub = sam3FindField(rep, "VALUE"); 803 | // 804 | if (strcmp(rs, "OK") == 0) { 805 | if (pub != NULL && sam3CheckValidKeyLength(pub)) { 806 | strcpy(ses->destkey, pub); 807 | strcpyerr(ses, NULL); 808 | res = 0; 809 | } 810 | } else if (rs[0]) { 811 | strcpyerr(ses, rs); 812 | } 813 | } 814 | } 815 | // 816 | sam3FreeFieldList(rep); 817 | sam3tcpDisconnect(fd); 818 | // 819 | return res; 820 | } 821 | return -1; 822 | } 823 | 824 | //////////////////////////////////////////////////////////////////////////////// 825 | static int sam3CloseConnectionInternal(Sam3Connection *conn) { 826 | return (conn->fd >= 0 ? sam3tcpDisconnect(conn->fd) : 0); 827 | } 828 | 829 | int sam3CloseConnection(Sam3Connection *conn) { 830 | if (conn != NULL) { 831 | int res = sam3CloseConnectionInternal(conn); 832 | // 833 | if (conn->ses != NULL) { 834 | for (Sam3Connection *p = NULL, *c = conn->ses->connlist; c != NULL; 835 | p = c, c = c->next) { 836 | if (c == conn) { 837 | if (p == NULL) 838 | conn->ses->connlist = c->next; 839 | else 840 | p->next = c->next; 841 | break; 842 | } 843 | } 844 | } 845 | free(conn); 846 | // 847 | return res; 848 | } 849 | return -1; 850 | } 851 | 852 | int sam3CloseSession(Sam3Session *ses) { 853 | if (ses != NULL) { 854 | for (Sam3Connection *n, *c = ses->connlist; c != NULL; c = n) { 855 | n = c->next; 856 | sam3CloseConnectionInternal(c); 857 | free(c); 858 | } 859 | if (ses->fwd_fd >= 0) 860 | sam3tcpDisconnect(ses->fwd_fd); 861 | if (ses->fd >= 0) 862 | sam3tcpDisconnect(ses->fd); 863 | memset(ses, 0, sizeof(Sam3Session)); 864 | ses->fd = -1; 865 | return 0; 866 | } 867 | return -1; 868 | } 869 | 870 | int sam3CreateSilentSession(Sam3Session *ses, const char *hostname, int port, 871 | const char *privkey, Sam3SessionType type, 872 | Sam3SigType sigType, const char *params) { 873 | int r = 874 | sam3CreateSession(ses, hostname, port, privkey, type, sigType, params); 875 | if (r != 0) { 876 | return r; 877 | } 878 | ses->silent = true; 879 | return 0; 880 | } 881 | 882 | int sam3CreateSession(Sam3Session *ses, const char *hostname, int port, 883 | const char *privkey, Sam3SessionType type, 884 | Sam3SigType sigType, const char *params) { 885 | if (ses != NULL) { 886 | static const char *typenames[3] = {"RAW", "DATAGRAM", "STREAM"}; 887 | static const char *sigtypes[5] = { 888 | "SIGNATURE_TYPE=DSA_SHA1", "SIGNATURE_TYPE=ECDSA_SHA256_P256", 889 | "SIGNATURE_TYPE=ECDSA_SHA384_P384", "SIGNATURE_TYPE=ECDSA_SHA512_P521", 890 | "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"}; 891 | 892 | SAMFieldList *rep; 893 | const char *v = NULL; 894 | const char *pdel = (params != NULL ? " " : ""); 895 | // 896 | memset(ses, 0, sizeof(Sam3Session)); 897 | ses->fd = -1; 898 | ses->fwd_fd = -1; 899 | ses->silent = false; 900 | // 901 | if (privkey != NULL && strlen(privkey) < SAM3_PRIVKEY_MIN_SIZE) 902 | goto error; 903 | if ((int)type < 0 || (int)type > 2) 904 | goto error; 905 | if (privkey == NULL) 906 | privkey = "TRANSIENT"; 907 | // 908 | ses->type = type; 909 | ses->sigType = sigType; 910 | ses->port = (type == SAM3_SESSION_STREAM ? (port ? port : 7656) : 7655); 911 | sam3GenChannelName(ses->channel, 32, 64); 912 | if (libsam3_debug) 913 | fprintf(stderr, "sam3CreateSession: channel=[%s]\n", ses->channel); 914 | // 915 | if ((ses->fd = sam3Handshake(hostname, port, &ses->ip)) < 0) 916 | goto error; 917 | // 918 | if (libsam3_debug) 919 | fprintf(stderr, "sam3CreateSession: creating session (%s)...\n", 920 | typenames[(int)type]); 921 | if (sam3tcpPrintf( 922 | ses->fd, "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s %s %s %s\n", 923 | typenames[(int)type], ses->channel, privkey, sigtypes[(int)sigType], 924 | pdel, (params != NULL ? params : "")) < 0) 925 | goto error; 926 | if ((rep = sam3ReadReply(ses->fd)) == NULL) 927 | goto error; 928 | if (!sam3IsGoodReply(rep, "SESSION", "STATUS", "RESULT", "OK") || 929 | (v = sam3FindField(rep, "DESTINATION")) == NULL || 930 | strlen(v) < SAM3_PRIVKEY_MIN_SIZE) { 931 | if (libsam3_debug) 932 | fprintf(stderr, "sam3CreateSession: invalid reply (%ld)...\n", 933 | (v != NULL ? strlen(v) : -1)); 934 | if (libsam3_debug) 935 | sam3DumpFieldList(rep); 936 | sam3FreeFieldList(rep); 937 | goto error; 938 | } 939 | // save our keys 940 | if (strlen(v) > SAM3_PRIVKEY_MAX_SIZE) { 941 | fprintf(stderr, "ERROR, Unexpected key size (%li)!\n", strlen(v)); 942 | goto error; 943 | } 944 | strcpy(ses->privkey, v); 945 | sam3FreeFieldList(rep); 946 | // get public key 947 | if (sam3tcpPrintf(ses->fd, "NAMING LOOKUP NAME=ME\n") < 0) 948 | goto error; 949 | if ((rep = sam3ReadReply(ses->fd)) == NULL) 950 | goto error; 951 | v = NULL; 952 | if (!sam3IsGoodReply(rep, "NAMING", "REPLY", "RESULT", "OK") || 953 | (v = sam3FindField(rep, "VALUE")) == NULL || 954 | !sam3CheckValidKeyLength(v)) { 955 | if (libsam3_debug) 956 | fprintf(stderr, "sam3CreateSession: invalid NAMING reply (%ld)...\n", 957 | (v != NULL ? strlen(v) : -1)); 958 | if (libsam3_debug) 959 | sam3DumpFieldList(rep); 960 | sam3FreeFieldList(rep); 961 | goto error; 962 | } 963 | strcpy(ses->pubkey, v); 964 | sam3FreeFieldList(rep); 965 | // 966 | if (libsam3_debug) 967 | fprintf(stderr, "sam3CreateSession: complete.\n"); 968 | return 0; 969 | } 970 | error: 971 | sam3CloseSession(ses); 972 | return -1; 973 | } 974 | 975 | Sam3Connection *sam3StreamConnect(Sam3Session *ses, const char *destkey) { 976 | if (ses != NULL) { 977 | SAMFieldList *rep; 978 | Sam3Connection *conn; 979 | // 980 | if (ses->type != SAM3_SESSION_STREAM) { 981 | strcpyerr(ses, "INVALID_SESSION_TYPE"); 982 | return NULL; 983 | } 984 | if (ses->fd < 0) { 985 | strcpyerr(ses, "INVALID_SESSION"); 986 | return NULL; 987 | } 988 | if (destkey == NULL || !sam3CheckValidKeyLength(destkey)) { 989 | strcpyerr(ses, "INVALID_KEY"); 990 | return NULL; 991 | } 992 | if ((conn = calloc(1, sizeof(Sam3Connection))) == NULL) { 993 | strcpyerr(ses, "NO_MEMORY"); 994 | return NULL; 995 | } 996 | if ((conn->fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { 997 | strcpyerr(ses, "IO_ERROR_SK"); 998 | goto error; 999 | } 1000 | if (sam3tcpPrintf(conn->fd, 1001 | "STREAM CONNECT ID=%s DESTINATION=%s SILENT=%s\n", 1002 | ses->channel, destkey, checkIsSilent(ses)) < 0) { 1003 | strcpyerr(ses, "IO_ERROR"); 1004 | goto error; 1005 | } 1006 | if ((rep = sam3ReadReply(conn->fd)) == NULL) { 1007 | strcpyerr(ses, "IO_ERROR"); 1008 | goto error; 1009 | } 1010 | if (!ses->silent) { 1011 | if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { 1012 | const char *v = sam3FindField(rep, "RESULT"); 1013 | // 1014 | strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR")); 1015 | sam3CloseConnectionInternal(conn); 1016 | free(conn); 1017 | conn = NULL; 1018 | } else { 1019 | // no error 1020 | strcpyerr(ses, NULL); 1021 | } 1022 | } 1023 | sam3FreeFieldList(rep); 1024 | if (conn != NULL) { 1025 | strcpy(conn->destkey, destkey); 1026 | conn->ses = ses; 1027 | conn->next = ses->connlist; 1028 | ses->connlist = conn; 1029 | } 1030 | return conn; 1031 | error: 1032 | sam3CloseConnectionInternal(conn); 1033 | free(conn); 1034 | return NULL; 1035 | } 1036 | return NULL; 1037 | } 1038 | 1039 | Sam3Connection *sam3StreamAccept(Sam3Session *ses) { 1040 | if (ses != NULL) { 1041 | SAMFieldList *rep = NULL; 1042 | char repstr[1024]; 1043 | Sam3Connection *conn; 1044 | // 1045 | if (ses->type != SAM3_SESSION_STREAM) { 1046 | strcpyerr(ses, "INVALID_SESSION_TYPE"); 1047 | return NULL; 1048 | } 1049 | if (ses->fd < 0) { 1050 | strcpyerr(ses, "INVALID_SESSION"); 1051 | return NULL; 1052 | } 1053 | if ((conn = calloc(1, sizeof(Sam3Connection))) == NULL) { 1054 | strcpyerr(ses, "NO_MEMORY"); 1055 | return NULL; 1056 | } 1057 | if ((conn->fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { 1058 | strcpyerr(ses, "IO_ERROR_SK"); 1059 | goto error; 1060 | } 1061 | if (sam3tcpPrintf(conn->fd, "STREAM ACCEPT ID=%s\n", ses->channel) < 0) { 1062 | strcpyerr(ses, "IO_ERROR_PF"); 1063 | goto error; 1064 | } 1065 | if ((rep = sam3ReadReply(conn->fd)) == NULL) { 1066 | strcpyerr(ses, "IO_ERROR_RP"); 1067 | goto error; 1068 | } 1069 | if (!ses->silent) { 1070 | if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { 1071 | const char *v = sam3FindField(rep, "RESULT"); 1072 | // 1073 | strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES")); 1074 | goto error; 1075 | } 1076 | } 1077 | if (sam3tcpReceiveStr(conn->fd, repstr, sizeof(repstr)) < 0) { 1078 | strcpyerr(ses, "IO_ERROR_RP1"); 1079 | goto error; 1080 | } 1081 | sam3FreeFieldList(rep); 1082 | if ((rep = sam3ParseReply(repstr)) != NULL) { 1083 | const char *v = sam3FindField(rep, "RESULT"); 1084 | // 1085 | strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES1")); 1086 | goto error; 1087 | } 1088 | if (!sam3CheckValidKeyLength(repstr)) { 1089 | strcpyerr(ses, "INVALID_KEY"); 1090 | goto error; 1091 | } 1092 | sam3FreeFieldList(rep); 1093 | strcpy(conn->destkey, repstr); 1094 | conn->ses = ses; 1095 | conn->next = ses->connlist; 1096 | ses->connlist = conn; 1097 | strcpyerr(ses, NULL); 1098 | return conn; 1099 | error: 1100 | if (rep != NULL) 1101 | sam3FreeFieldList(rep); 1102 | sam3CloseConnectionInternal(conn); 1103 | free(conn); 1104 | return NULL; 1105 | } 1106 | return NULL; 1107 | } 1108 | 1109 | const char *checkIsSilent(Sam3Session *ses) { 1110 | if (ses->silent == true) { 1111 | return "true"; 1112 | } else { 1113 | return "false"; 1114 | } 1115 | } 1116 | 1117 | int sam3StreamForward(Sam3Session *ses, const char *hostname, int port) { 1118 | if (ses != NULL) { 1119 | SAMFieldList *rep = NULL; 1120 | // 1121 | if (ses->type != SAM3_SESSION_STREAM) { 1122 | strcpyerr(ses, "INVALID_SESSION_TYPE"); 1123 | return -1; 1124 | } 1125 | if (ses->fd < 0) { 1126 | strcpyerr(ses, "INVALID_SESSION"); 1127 | return -1; 1128 | } 1129 | if (ses->fwd_fd >= 0) { 1130 | strcpyerr(ses, "DUPLICATE_FORWARD"); 1131 | return -1; 1132 | } 1133 | if ((ses->fwd_fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { 1134 | strcpyerr(ses, "IO_ERROR_SK"); 1135 | goto error; 1136 | } 1137 | if (sam3tcpPrintf(ses->fwd_fd, 1138 | "STREAM FORWARD ID=%s PORT=%d HOST=%s SILENT=%s\n", 1139 | ses->channel, port, hostname, checkIsSilent(ses)) < 0) { 1140 | strcpyerr(ses, "IO_ERROR_PF"); 1141 | goto error; 1142 | } 1143 | if ((rep = sam3ReadReply(ses->fwd_fd)) == NULL) { 1144 | strcpyerr(ses, "IO_ERROR_RP"); 1145 | goto error; 1146 | } 1147 | if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { 1148 | const char *v = sam3FindField(rep, "RESULT"); 1149 | // 1150 | strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES")); 1151 | goto error; 1152 | } 1153 | sam3FreeFieldList(rep); 1154 | strcpyerr(ses, NULL); 1155 | return 0; 1156 | error: 1157 | if (rep != NULL) 1158 | sam3FreeFieldList(rep); 1159 | return -1; 1160 | } 1161 | return -1; 1162 | } 1163 | 1164 | int sam3DatagramSend(Sam3Session *ses, const char *destkey, const void *buf, 1165 | size_t bufsize) { 1166 | if (ses != NULL) { 1167 | char *dbuf; 1168 | int res, dbufsz; 1169 | // 1170 | if (ses->type == SAM3_SESSION_STREAM) { 1171 | strcpyerr(ses, "INVALID_SESSION_TYPE"); 1172 | return -1; 1173 | } 1174 | if (ses->fd < 0) { 1175 | strcpyerr(ses, "INVALID_SESSION"); 1176 | return -1; 1177 | } 1178 | if (destkey == NULL || !sam3CheckValidKeyLength(destkey)) { 1179 | strcpyerr(ses, "INVALID_KEY"); 1180 | return -1; 1181 | } 1182 | if (buf == NULL || bufsize < 1 || bufsize > 31744) { 1183 | strcpyerr(ses, "INVALID_DATA"); 1184 | return -1; 1185 | } 1186 | dbufsz = bufsize + 4 + strlen(destkey) + 1 + strlen(ses->channel) + 1; 1187 | if ((dbuf = malloc(dbufsz)) == NULL) { 1188 | strcpyerr(ses, "OUT_OF_MEMORY"); 1189 | return -1; 1190 | } 1191 | memset(dbuf, 0, dbufsz); 1192 | sprintf(dbuf, "3.0 %s %s\n", ses->channel, destkey); 1193 | memcpy(dbuf + strlen(dbuf), buf, bufsize); 1194 | res = sam3udpSendToIP(ses->ip, ses->port, dbuf, dbufsz); 1195 | free(dbuf); 1196 | strcpyerr(ses, (res < 0 ? "IO_ERROR" : NULL)); 1197 | return (res < 0 ? -1 : 0); 1198 | } 1199 | return -1; 1200 | } 1201 | 1202 | ssize_t sam3DatagramReceive(Sam3Session *ses, void *buf, size_t bufsize) { 1203 | if (ses != NULL) { 1204 | SAMFieldList *rep; 1205 | const char *v; 1206 | ssize_t size = 0; 1207 | // 1208 | if (ses->type == SAM3_SESSION_STREAM) { 1209 | strcpyerr(ses, "INVALID_SESSION_TYPE"); 1210 | return -1; 1211 | } 1212 | if (ses->fd < 0) { 1213 | strcpyerr(ses, "INVALID_SESSION"); 1214 | return -1; 1215 | } 1216 | if (buf == NULL || bufsize < 1) { 1217 | strcpyerr(ses, "INVALID_BUFFER"); 1218 | return -1; 1219 | } 1220 | if ((rep = sam3ReadReply(ses->fd)) == NULL) { 1221 | strcpyerr(ses, "IO_ERROR"); 1222 | return -1; 1223 | } 1224 | if (!sam3IsGoodReply(rep, "DATAGRAM", "RECEIVED", "SIZE", NULL)) { 1225 | strcpyerr(ses, "I2P_ERROR"); 1226 | sam3FreeFieldList(rep); 1227 | return -1; 1228 | } 1229 | // 1230 | if ((v = sam3FindField(rep, "DESTINATION")) != NULL && 1231 | sam3CheckValidKeyLength(v)) 1232 | strncpy(ses->destkey, v, sizeof(ses->destkey)); 1233 | v = sam3FindField(rep, "SIZE"); // we have this field -- for sure 1234 | if (!v[0] || !isdigit(*v)) { 1235 | strcpyerr(ses, "I2P_ERROR_SIZE"); 1236 | sam3FreeFieldList(rep); 1237 | return -1; 1238 | } 1239 | // 1240 | while (*v && isdigit(*v)) { 1241 | if ((size = size * 10 + v[0] - '0') > bufsize) { 1242 | strcpyerr(ses, "I2P_ERROR_BUFFER_TOO_SMALL"); 1243 | sam3FreeFieldList(rep); 1244 | return -1; 1245 | } 1246 | ++v; 1247 | } 1248 | // 1249 | if (*v) { 1250 | strcpyerr(ses, "I2P_ERROR_SIZE"); 1251 | sam3FreeFieldList(rep); 1252 | return -1; 1253 | } 1254 | sam3FreeFieldList(rep); 1255 | // 1256 | if (sam3tcpReceive(ses->fd, buf, size) != size) { 1257 | strcpyerr(ses, "IO_ERROR"); 1258 | return -1; 1259 | } 1260 | strcpyerr(ses, NULL); 1261 | return size; 1262 | } 1263 | return -1; 1264 | } 1265 | 1266 | //////////////////////////////////////////////////////////////////////////////// 1267 | // output 8 bytes for every 5 input 1268 | // return size or <0 on error 1269 | ssize_t sam3Base32Encode(char *dest, size_t destsz, const void *srcbuf, 1270 | size_t srcsize) { 1271 | if (dest != NULL && srcbuf != NULL && srcsize >= 0) { 1272 | static const char *const b32chars = "abcdefghijklmnopqrstuvwxyz234567="; 1273 | const unsigned char *src = (const unsigned char *)srcbuf; 1274 | ssize_t destsize = 0; 1275 | // 1276 | while (srcsize > 0) { 1277 | int blksize = (srcsize < 5 ? srcsize : 5); 1278 | unsigned char n[8]; 1279 | // 1280 | memset(n, 0, sizeof(n)); 1281 | switch (blksize) { 1282 | case 5: 1283 | n[7] = (src[4] & 0x1f); 1284 | n[6] = ((src[4] & 0xe0) >> 5); 1285 | case 4: 1286 | n[6] |= ((src[3] & 0x03) << 3); 1287 | n[5] = ((src[3] & 0x7c) >> 2); 1288 | n[4] = ((src[3] & 0x80) >> 7); 1289 | case 3: 1290 | n[4] |= ((src[2] & 0x0f) << 1); 1291 | n[3] = ((src[2] & 0xf0) >> 4); 1292 | case 2: 1293 | n[3] |= ((src[1] & 0x01) << 4); 1294 | n[2] = ((src[1] & 0x3e) >> 1); 1295 | n[1] = ((src[1] & 0xc0) >> 6); 1296 | case 1: 1297 | n[1] |= ((src[0] & 0x07) << 2); 1298 | n[0] = ((src[0] & 0xf8) >> 3); 1299 | break; 1300 | } 1301 | src += blksize; 1302 | srcsize -= blksize; 1303 | // pad 1304 | switch (blksize) { 1305 | case 1: 1306 | n[2] = n[3] = 32; 1307 | case 2: 1308 | n[4] = 32; 1309 | case 3: 1310 | n[5] = n[6] = 32; 1311 | case 4: 1312 | n[7] = 32; 1313 | case 5: 1314 | break; 1315 | } 1316 | // output 1317 | if (destsize + 8 <= destsz) { 1318 | for (int f = 0; f < 8; ++f) 1319 | *dest++ = b32chars[n[f]]; 1320 | } 1321 | destsize += 8; 1322 | } 1323 | if (destsize <= destsz) { 1324 | *dest++ = 0; // make valid asciiz string 1325 | } 1326 | return destsize; 1327 | } 1328 | return -1; 1329 | } 1330 | 1331 | //////////////////////////////////////////////////////////////////////////////// 1332 | // output 8 bytes for every 5 input 1333 | // return size or <0 on error 1334 | ssize_t sam3Base32Decode(char *dest, size_t destsz, const void *srcbuf, 1335 | size_t srcsize) { 1336 | if (dest != NULL && srcbuf != NULL && srcsize >= 0) { 1337 | static const char *const b32chars = "abcdefghijklmnopqrstuvwxyz234567="; 1338 | const unsigned char *src = (const unsigned char *)srcbuf; 1339 | int destsize = 0; 1340 | // 1341 | while (srcsize > 0) { 1342 | int blksize = (srcsize < 5 ? srcsize : 5); 1343 | unsigned char n[8]; 1344 | // 1345 | memset(n, 0, sizeof(n)); 1346 | switch (blksize) { 1347 | case 5: 1348 | n[7] = (src[4] & 0x1f); 1349 | n[6] = ((src[4] & 0xe0) >> 5); 1350 | case 4: 1351 | n[6] |= ((src[3] & 0x03) << 3); 1352 | n[5] = ((src[3] & 0x7c) >> 2); 1353 | n[4] = ((src[3] & 0x80) >> 7); 1354 | case 3: 1355 | n[4] |= ((src[2] & 0x0f) << 1); 1356 | n[3] = ((src[2] & 0xf0) >> 4); 1357 | case 2: 1358 | n[3] |= ((src[1] & 0x01) << 4); 1359 | n[2] = ((src[1] & 0x3e) >> 1); 1360 | n[1] = ((src[1] & 0xc0) >> 6); 1361 | case 1: 1362 | n[1] |= ((src[0] & 0x07) << 2); 1363 | n[0] = ((src[0] & 0xf8) >> 3); 1364 | break; 1365 | } 1366 | src += blksize; 1367 | srcsize -= blksize; 1368 | // pad 1369 | switch (blksize) { 1370 | case 1: 1371 | n[2] = n[3] = 32; 1372 | case 2: 1373 | n[4] = 32; 1374 | case 3: 1375 | n[5] = n[6] = 32; 1376 | case 4: 1377 | n[7] = 32; 1378 | case 5: 1379 | break; 1380 | } 1381 | // output 1382 | if (destsize + 8 <= destsz) { 1383 | for (int f = 0; f < 8; ++f) 1384 | *dest++ = b32chars[n[f]]; 1385 | } 1386 | destsize += 8; 1387 | } 1388 | if (destsize <= destsz) { 1389 | *dest++ = 0; // make valid asciiz string 1390 | } 1391 | return destsize; 1392 | } 1393 | return -1; 1394 | } 1395 | -------------------------------------------------------------------------------- /src/libsam3/libsam3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | #ifndef LIBSAM3_H 26 | #define LIBSAM3_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef _SSIZE_T_DEFINED 34 | #define _SSIZE_T_DEFINED 35 | #undef ssize_t 36 | #ifdef _WIN64 37 | typedef signed int64 ssize_t; 38 | typedef int ssize_t; 39 | #endif /* _WIN64 */ 40 | #endif /* _SSIZE_T_DEFINED */ 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | //////////////////////////////////////////////////////////////////////////////// 47 | extern int libsam3_debug; 48 | 49 | //////////////////////////////////////////////////////////////////////////////// 50 | #define SAM3_HOST_DEFAULT (NULL) 51 | #define SAM3_PORT_DEFAULT (0) 52 | 53 | #define SAM3_DESTINATION_TRANSIENT (NULL) 54 | 55 | #define SAM3_PUBKEY_SIZE (516) 56 | #define SAM3_CERT_SIZE (100) 57 | #define SAM3_PRIVKEY_MIN_SIZE (884) 58 | #define SAM3_PRIVKEY_MAX_SIZE (1024) 59 | 60 | //////////////////////////////////////////////////////////////////////////////// 61 | /* returns fd or -1 */ 62 | /* 'ip': host IP; can be NULL */ 63 | extern int sam3tcpConnect(const char *hostname, int port, uint32_t *ip); 64 | extern int sam3tcpConnectIP(uint32_t ip, int port); 65 | 66 | /* <0: error; 0: ok */ 67 | extern int sam3tcpDisconnect(int fd); 68 | 69 | /* <0: error; 0: ok */ 70 | extern int sam3tcpSetTimeoutSend(int fd, int timeoutms); 71 | 72 | /* <0: error; 0: ok */ 73 | extern int sam3tcpSetTimeoutReceive(int fd, int timeoutms); 74 | 75 | /* <0: error; 0: ok */ 76 | /* sends the whole buffer */ 77 | extern int sam3tcpSend(int fd, const void *buf, size_t bufSize); 78 | 79 | /* <0: received (-res) bytes; read error */ 80 | /* can return less that requesten bytes even if `allowPartial` is 0 when 81 | * connection is closed */ 82 | extern ssize_t sam3tcpReceiveEx(int fd, void *buf, size_t bufSize, 83 | int allowPartial); 84 | 85 | extern ssize_t sam3tcpReceive(int fd, void *buf, size_t bufSize); 86 | 87 | extern int sam3tcpPrintf(int fd, const char *fmt, ...) 88 | __attribute__((format(printf, 2, 3))); 89 | 90 | extern int sam3tcpReceiveStr(int fd, char *dest, size_t maxSize); 91 | 92 | /* pass NULL for 'localhost' and 0 for 7655 */ 93 | /* 'ip': host IP; can be NULL */ 94 | extern int sam3udpSendTo(const char *hostname, int port, const void *buf, 95 | size_t bufSize, uint32_t *ip); 96 | extern int sam3udpSendToIP(uint32_t ip, int port, const void *buf, 97 | size_t bufSize); 98 | 99 | //////////////////////////////////////////////////////////////////////////////// 100 | typedef struct SAMFieldList { 101 | char *name; 102 | char *value; 103 | struct SAMFieldList *next; 104 | } SAMFieldList; 105 | 106 | extern void sam3FreeFieldList(SAMFieldList *list); 107 | extern void sam3DumpFieldList(const SAMFieldList *list); 108 | 109 | /* read and parse SAM reply */ 110 | /* NULL: error; else: list of fields */ 111 | /* first item is always 2-word reply, with first word in name and second in 112 | * value */ 113 | extern SAMFieldList *sam3ReadReply(int fd); 114 | 115 | extern SAMFieldList *sam3ParseReply(const char *rep); 116 | 117 | /* 118 | * example: 119 | * r0: 'HELLO' 120 | * r1: 'REPLY' 121 | * field: NULL or 'RESULT' 122 | * VALUE: NULL or 'OK' 123 | * returns bool 124 | */ 125 | extern int sam3IsGoodReply(const SAMFieldList *list, const char *r0, 126 | const char *r1, const char *field, 127 | const char *value); 128 | 129 | extern const char *sam3FindField(const SAMFieldList *list, const char *field); 130 | 131 | //////////////////////////////////////////////////////////////////////////////// 132 | /* pass NULL for 'localhost' and 0 for 7656 */ 133 | /* returns <0 on error or socket fd on success */ 134 | extern int sam3Handshake(const char *hostname, int port, uint32_t *ip); 135 | extern int sam3HandshakeIP(uint32_t ip, int port); 136 | 137 | //////////////////////////////////////////////////////////////////////////////// 138 | typedef enum { 139 | SAM3_SESSION_RAW, 140 | SAM3_SESSION_DGRAM, 141 | SAM3_SESSION_STREAM 142 | } Sam3SessionType; 143 | 144 | typedef enum { 145 | DSA_SHA1, 146 | ECDSA_SHA256_P256, 147 | ECDSA_SHA384_P384, 148 | ECDSA_SHA512_P521, 149 | EdDSA_SHA512_Ed25519 150 | } Sam3SigType; 151 | 152 | typedef struct Sam3Session { 153 | Sam3SessionType type; 154 | Sam3SigType sigType; 155 | int fd; 156 | char privkey[SAM3_PRIVKEY_MAX_SIZE + 1]; // destination private key (asciiz) 157 | char pubkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + 158 | 1]; // destination public key (asciiz) 159 | char channel[66]; // name of this sam session (asciiz) 160 | char destkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + 161 | 1]; // for DGRAM sessions (asciiz) 162 | // int destsig; 163 | char error[32]; // error message (asciiz) 164 | uint32_t ip; 165 | int port; // this will be changed to UDP port for DRAM/RAW (can be 0) 166 | struct Sam3Connection *connlist; // list of opened connections 167 | int fwd_fd; 168 | bool silent; 169 | } Sam3Session; 170 | 171 | typedef struct Sam3Connection { 172 | Sam3Session *ses; 173 | struct Sam3Connection *next; 174 | int fd; 175 | char destkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + 176 | 1]; // remote destination public key (asciiz) 177 | int destcert; 178 | char error[32]; // error message (asciiz) 179 | } Sam3Connection; 180 | 181 | //////////////////////////////////////////////////////////////////////////////// 182 | /* 183 | * create SAM session 184 | * pass NULL as hostname for 'localhost' and 0 as port for 7656 185 | * pass NULL as privkey to create TRANSIENT session 186 | * 'params' can be NULL 187 | * see http://www.i2p2.i2p/i2cp.html#options for common options, 188 | * and http://www.i2p2.i2p/streaming.html#options for STREAM options 189 | * if result<0: error, 'ses' fields are undefined, no need to call 190 | * sam3CloseSession() if result==0: ok, all 'ses' fields are filled 191 | * TODO: don't clear 'error' field on error (and set it to something meaningful) 192 | */ 193 | extern int sam3CreateSession(Sam3Session *ses, const char *hostname, int port, 194 | const char *privkey, Sam3SessionType type, 195 | Sam3SigType sigType, const char *params); 196 | 197 | /* 198 | * create SAM session with SILENT=True 199 | * pass NULL as hostname for 'localhost' and 0 as port for 7656 200 | * pass NULL as privkey to create TRANSIENT session 201 | * 'params' can be NULL 202 | * see http://www.i2p2.i2p/i2cp.html#options for common options, 203 | * and http://www.i2p2.i2p/streaming.html#options for STREAM options 204 | * if result<0: error, 'ses' fields are undefined, no need to call 205 | * sam3CloseSession() if result==0: ok, all 'ses' fields are filled 206 | * TODO: don't clear 'error' field on error (and set it to something meaningful) 207 | */ 208 | extern int sam3CreateSilentSession(Sam3Session *ses, const char *hostname, 209 | int port, const char *privkey, 210 | Sam3SessionType type, Sam3SigType sigType, 211 | const char *params); 212 | 213 | /* 214 | * close SAM session (and all it's connections) 215 | * returns <0 on error, 0 on ok 216 | * 'ses' must be properly initialized 217 | */ 218 | extern int sam3CloseSession(Sam3Session *ses); 219 | 220 | /* 221 | * check to see if a SAM session is silent and output 222 | * characters for use with sam3tcpPrintf() checkIsSilent 223 | */ 224 | 225 | const char *checkIsSilent(Sam3Session *ses); 226 | 227 | /* 228 | * Check to make sure that the destination in use is of a valid length, returns 229 | * 1 if true and 0 if false. 230 | */ 231 | int sam3CheckValidKeyLength(const char *pubkey); 232 | 233 | /* 234 | * open stream connection to 'destkey' endpoint 235 | * 'destkey' is 516-byte public key (asciiz) 236 | * returns <0 on error, fd on ok 237 | * you still have to call sam3CloseSession() on failure 238 | * sets ses->error on error 239 | */ 240 | extern Sam3Connection *sam3StreamConnect(Sam3Session *ses, const char *destkey); 241 | 242 | /* 243 | * accepts stream connection and sets 'destkey' 244 | * 'destkey' is 516-byte public key 245 | * returns <0 on error, fd on ok 246 | * you still have to call sam3CloseSession() on failure 247 | * sets ses->error on error 248 | * note that there is no timeouts for now, but you can use sam3tcpSetTimeout*() 249 | */ 250 | extern Sam3Connection *sam3StreamAccept(Sam3Session *ses); 251 | 252 | /* 253 | * sets up forwarding stream connection 254 | * returns <0 on error, 0 on ok 255 | * you still have to call sam3CloseSession() on failure 256 | * sets ses->error on error 257 | * note that there is no timeouts for now, but you can use sam3tcpSetTimeout*() 258 | */ 259 | extern int sam3StreamForward(Sam3Session *ses, const char *hostname, int port); 260 | 261 | /* 262 | * close SAM connection 263 | * returns <0 on error, 0 on ok 264 | * 'conn' must be properly initialized 265 | * 'conn' is invalid after call 266 | */ 267 | extern int sam3CloseConnection(Sam3Connection *conn); 268 | 269 | //////////////////////////////////////////////////////////////////////////////// 270 | /* 271 | * generate new keypair 272 | * fills 'privkey' and 'pubkey' only 273 | * you should not call sam3CloseSession() on 'ses' 274 | * will not set 'error' field 275 | * returns <0 on error, 0 on ok 276 | */ 277 | extern int sam3GenerateKeys(Sam3Session *ses, const char *hostname, int port, 278 | int sigType); 279 | 280 | /* 281 | * do name lookup (something like gethostbyname()) 282 | * fills 'destkey' only 283 | * you should not call sam3CloseSession() on 'ses' 284 | * will set 'error' field 285 | * returns <0 on error, 0 on ok 286 | */ 287 | extern int sam3NameLookup(Sam3Session *ses, const char *hostname, int port, 288 | const char *name); 289 | 290 | //////////////////////////////////////////////////////////////////////////////// 291 | /* 292 | * sends datagram to 'destkey' endpoint 293 | * 'destkey' is 516-byte public key 294 | * returns <0 on error, 0 on ok 295 | * you still have to call sam3CloseSession() on failure 296 | * sets ses->error on error 297 | * don't send datagrams bigger than 31KB! 298 | */ 299 | extern int sam3DatagramSend(Sam3Session *ses, const char *destkey, 300 | const void *buf, size_t bufsize); 301 | 302 | /* 303 | * receives datagram and sets 'destkey' to source pubkey (if not RAW) 304 | * returns <0 on error (buffer too small is error too) or number of bytes 305 | * written to 'buf' you still have to call sam3CloseSession() on failure sets 306 | * ses->error on error will necer receive datagrams bigger than 31KB (32KB for 307 | * RAW) 308 | */ 309 | extern ssize_t sam3DatagramReceive(Sam3Session *ses, void *buf, size_t bufsize); 310 | 311 | /* 312 | * generate random sam channel name 313 | * return the size of the string 314 | */ 315 | extern size_t sam3GenChannelName(char *dest, size_t minlen, size_t maxlen); 316 | 317 | //////////////////////////////////////////////////////////////////////////////// 318 | // NOT including '\0' terminator 319 | static inline size_t sam3Base32EncodedLength(size_t size) { 320 | return (((size + 5 - 1) / 5) * 8); 321 | } 322 | 323 | // output 8 bytes for every 5 input 324 | // return size or <0 on error 325 | extern ssize_t sam3Base32Encode(char *dest, size_t destsz, const void *srcbuf, 326 | size_t srcsize); 327 | 328 | #ifdef __cplusplus 329 | } 330 | #endif 331 | #endif 332 | -------------------------------------------------------------------------------- /src/libsam3a/libsam3a.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include "libsam3a.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #ifdef __MINGW32__ 39 | //#include 40 | #include 41 | #include 42 | #include 43 | #ifndef MSG_NOSIGNAL 44 | #define MSG_NOSIGNAL 0 45 | #endif 46 | #ifndef SHUT_RDWR 47 | #define SHUT_RDWR 2 48 | #endif 49 | #endif 50 | 51 | #if defined(__unix__) && !defined(__APPLE__) 52 | #include 53 | #endif 54 | 55 | #if defined(__unix__) || defined(__APPLE__) 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #endif 63 | 64 | #if defined(__APPLE__) 65 | #include 66 | #include 67 | #ifndef SOCK_CLOEXEC 68 | #define SOCK_CLOEXEC 0 69 | #endif 70 | #ifndef SOCK_NONBLOCK 71 | #include 72 | #define SOCK_NONBLOCK O_NONBLOCK 73 | #endif 74 | #ifndef MSG_NOSIGNAL 75 | #define MSG_NOSIGNAL 0 76 | #endif 77 | uint32_t TickCount() { 78 | uint64_t mat = mach_absolute_time(); 79 | uint32_t mul = 0x80d9594e; 80 | return ((((0xffffffff & mat) * mul) >> 32) + (mat >> 32) * mul) >> 23; 81 | } 82 | #endif 83 | 84 | //////////////////////////////////////////////////////////////////////////////// 85 | int libsam3a_debug = 0; 86 | 87 | #define DEFAULT_TCP_PORT (7656) 88 | #define DEFAULT_UDP_PORT (7655) 89 | 90 | //////////////////////////////////////////////////////////////////////////////// 91 | extern uint64_t sam3atimeval2ms(const struct timeval *tv) { 92 | return ((uint64_t)tv->tv_sec) * 1000 + ((uint64_t)tv->tv_usec) / 1000; 93 | } 94 | 95 | extern void sam3ams2timeval(struct timeval *tv, uint64_t ms) { 96 | tv->tv_sec = ms / 1000; 97 | tv->tv_usec = (ms % 1000) * 1000; 98 | } 99 | 100 | //////////////////////////////////////////////////////////////////////////////// 101 | static inline int isValidKeyChar(char ch) { 102 | return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || 103 | (ch >= '0' && ch <= '9') || ch == '-' || ch == '~'; 104 | } 105 | 106 | int sam3aIsValidPubKey(const char *key) { 107 | if (key != NULL && strlen(key) == SAM3A_PUBKEY_SIZE) { 108 | for (int f = 0; f < SAM3A_PUBKEY_SIZE; ++f) 109 | if (!isValidKeyChar(key[f])) 110 | return 0; 111 | return 1; 112 | } 113 | return 0; 114 | } 115 | 116 | int sam3aIsValidPrivKey(const char *key) { 117 | if (key != NULL && strlen(key) == SAM3A_PRIVKEY_SIZE) { 118 | for (int f = 0; f < SAM3A_PRIVKEY_SIZE; ++f) 119 | if (!isValidKeyChar(key[f])) 120 | return 0; 121 | return 1; 122 | } 123 | return 0; 124 | } 125 | 126 | //////////////////////////////////////////////////////////////////////////////// 127 | /* 128 | static int sam3aSocketSetTimeoutSend (int fd, int timeoutms) { 129 | if (fd >= 0 && timeoutms >= 0) { 130 | struct timeval tv; 131 | // 132 | ms2timeval(&tv, timeoutms); 133 | return (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0 ? -1 : 134 | 0); 135 | } 136 | return -1; 137 | } 138 | 139 | 140 | static int sam3aSocketSetTimeoutReceive (int fd, int timeoutms) { 141 | if (fd >= 0 && timeoutms >= 0) { 142 | struct timeval tv; 143 | // 144 | ms2timeval(&tv, timeoutms); 145 | return (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ? -1 : 146 | 0); 147 | } 148 | return -1; 149 | } 150 | */ 151 | 152 | static int sam3aBytesAvail(int fd) { 153 | int av = 0; 154 | // 155 | if (ioctl(fd, FIONREAD, &av) < 0) 156 | return -1; 157 | return av; 158 | } 159 | 160 | static uint32_t sam3aResolveHost(const char *hostname) { 161 | struct hostent *host; 162 | // 163 | if (hostname == NULL || !hostname[0]) 164 | return 0; 165 | if ((host = gethostbyname(hostname)) == NULL || host->h_name == NULL || 166 | !host->h_addr_list[0][0]) { 167 | if (libsam3a_debug) 168 | fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); 169 | return 0; 170 | } 171 | return ((struct in_addr *)host->h_addr_list[0])->s_addr; 172 | } 173 | 174 | static int sam3aConnect(uint32_t ip, int port, int *complete) { 175 | int fd, val = 1; 176 | // 177 | if (complete != NULL) 178 | *complete = 0; 179 | if (ip == 0 || ip == 0xffffffffUL || port < 1 || port > 65535) 180 | return -1; 181 | // 182 | // yes, this is Linux-specific; you know what? i don't care. 183 | if ((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) < 0) 184 | return -1; 185 | // 186 | setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); 187 | // 188 | for (;;) { 189 | struct sockaddr_in addr; 190 | // 191 | memset(&addr, 0, sizeof(addr)); 192 | addr.sin_family = AF_INET; 193 | addr.sin_port = htons(port); 194 | addr.sin_addr.s_addr = ip; 195 | // 196 | if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { 197 | if (errno == EINPROGRESS) 198 | break; // the process is started 199 | if (errno != EINTR) { 200 | close(fd); 201 | return -1; 202 | } 203 | } else { 204 | // connection complete 205 | if (complete != NULL) 206 | *complete = 1; 207 | break; 208 | } 209 | } 210 | // 211 | return fd; 212 | } 213 | 214 | // <0: error; 0: ok 215 | static int sam3aDisconnect(int fd) { 216 | if (fd >= 0) { 217 | shutdown(fd, SHUT_RDWR); 218 | return close(fd); 219 | } 220 | // 221 | return -1; 222 | } 223 | 224 | //////////////////////////////////////////////////////////////////////////////// 225 | // <0: error; >=0: bytes sent 226 | static int sam3aSendBytes(int fd, const void *buf, int bufSize) { 227 | const char *c = (const char *)buf; 228 | int total = 0; 229 | // 230 | if (fd < 0 || (buf == NULL && bufSize > 0)) 231 | return -1; 232 | // 233 | while (bufSize > 0) { 234 | int wr = send(fd, c, bufSize, MSG_NOSIGNAL); 235 | // 236 | if (wr < 0) { 237 | if (errno == EINTR) 238 | continue; // interrupted by signal 239 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 240 | // bufSize is too big 241 | if (bufSize == 1) 242 | break; // can't send anything 243 | // try to send a half of a buffer 244 | if ((wr = sam3aSendBytes(fd, c, bufSize / 2)) < 0) 245 | return wr; // error 246 | } else { 247 | return -1; // alas 248 | } 249 | } 250 | // 251 | if (wr == 0) 252 | break; // can't send anything 253 | c += wr; 254 | bufSize -= wr; 255 | total += wr; 256 | } 257 | // 258 | return total; 259 | } 260 | 261 | /* <0: error; >=0: bytes received */ 262 | /* note that you should call this function when there is some bytes to read, so 263 | * 0 means 'connection closed' */ 264 | /* 265 | static int sam3aReceive (int fd, void *buf, int bufSize) { 266 | char *c = (char *)buf; 267 | int total = 0; 268 | // 269 | if (fd < 0 || (buf == NULL && bufSize > 0)) return -1; 270 | // 271 | while (bufSize > 0) { 272 | int av = sam3aBytesAvail(fd), rd; 273 | // 274 | if (av == 0) break; // no more 275 | if (av > bufSize) av = bufSize; 276 | rd = recv(fd, c, av, 0); 277 | if (rd < 0) { 278 | if (errno == EINTR) continue; // interrupted by signal 279 | if (errno == EAGAIN || errno == EWOULDBLOCK) break; // the thing that 280 | should not be return -1; // error 281 | } 282 | if (rd == 0) break; 283 | c += rd; 284 | bufSize -= rd; 285 | total += rd; 286 | } 287 | // 288 | return total; 289 | } 290 | */ 291 | 292 | //////////////////////////////////////////////////////////////////////////////// 293 | char *sam3PrintfVA(int *plen, const char *fmt, va_list app) { 294 | char buf[1024], *p = buf; 295 | int size = sizeof(buf) - 1, len = 0; 296 | // 297 | if (plen != NULL) 298 | *plen = 0; 299 | for (;;) { 300 | va_list ap; 301 | char *np; 302 | int n; 303 | // 304 | va_copy(ap, app); 305 | n = vsnprintf(p, size, fmt, ap); 306 | va_end(ap); 307 | // 308 | if (n > -1 && n < size) { 309 | len = n; 310 | break; 311 | } 312 | if (n > -1) 313 | size = n + 1; 314 | else 315 | size *= 2; 316 | if (p == buf) { 317 | if ((p = malloc(size)) == NULL) 318 | return NULL; 319 | } else { 320 | if ((np = realloc(p, size)) == NULL) { 321 | free(p); 322 | return NULL; 323 | } 324 | p = np; 325 | } 326 | } 327 | // 328 | if (p == buf) { 329 | if ((p = malloc(len + 1)) == NULL) 330 | return NULL; 331 | memcpy(p, buf, len + 1); 332 | } 333 | if (plen != NULL) 334 | *plen = len; 335 | return p; 336 | } 337 | 338 | __attribute__((format(printf, 2, 3))) char *sam3Printf(int *plen, 339 | const char *fmt, ...) { 340 | va_list ap; 341 | char *res; 342 | // 343 | va_start(ap, fmt); 344 | res = sam3PrintfVA(plen, fmt, ap); 345 | va_end(ap); 346 | // 347 | return res; 348 | } 349 | 350 | /* 351 | * check if we have EOL in received socket data 352 | * this function should be called when sam3aBytesAvail() result > 0 353 | * return: <0: error; 0: no EOL in bytes2check, else: # of bytes to EOL 354 | * (including EOL itself) 355 | */ 356 | /* 357 | static int sam3aCheckEOL (int fd, int bytes2check) { 358 | char *d = dest; 359 | // 360 | if (bytes2check < 0 || fd < 0) return -1; 361 | memset(dest, 0, maxSize); 362 | while (maxSize > 1) { 363 | char *e; 364 | int rd = recv(fd, d, maxSize-1, MSG_PEEK); 365 | // 366 | if (rd < 0 && errno == EINTR) continue; // interrupted by signal 367 | if (rd == 0) { 368 | rd = recv(fd, d, 1, 0); 369 | if (rd < 0 && errno == EINTR) continue; // interrupted by signal 370 | if (d[0] == '\n') { 371 | d[0] = 0; // remove '\n' 372 | return 0; 373 | } 374 | } else { 375 | if (rd < 0) return -1; // error or connection closed; alas 376 | } 377 | // check for EOL 378 | d[maxSize-1] = 0; 379 | if ((e = strchr(d, '\n')) != NULL) { 380 | rd = e-d+1; // bytes to receive 381 | if (sam3atcpReceive(fd, d, rd) < 0) return -1; // alas 382 | d[rd-1] = 0; // remove '\n' 383 | return 0; // done 384 | } else { 385 | // let's receive this part and go on 386 | if (sam3atcpReceive(fd, d, rd) < 0) return -1; // alas 387 | maxSize -= rd; 388 | d += rd; 389 | } 390 | } 391 | // alas, the string is too big 392 | return -1; 393 | } 394 | */ 395 | 396 | //////////////////////////////////////////////////////////////////////////////// 397 | /* 398 | int sam3audpSendToIP (uint32_t ip, int port, const void *buf, int bufSize) { 399 | struct sockaddr_in addr; 400 | int fd, res; 401 | // 402 | if (buf == NULL || bufSize < 1) return -1; 403 | if (port < 1 || port > 65535) port = 7655; 404 | // 405 | if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { 406 | if (libsam3a_debug) fprintf(stderr, "ERROR: can't create socket\n"); 407 | return -1; 408 | } 409 | // 410 | memset(&addr, 0, sizeof(addr)); 411 | addr.sin_family = AF_INET; 412 | addr.sin_port = htons(port); 413 | addr.sin_addr.s_addr = ip; 414 | // 415 | res = sendto(fd, buf, bufSize, 0, (struct sockaddr *)&addr, sizeof(addr)); 416 | // 417 | if (res < 0) { 418 | if (libsam3a_debug) { 419 | res = errno; 420 | fprintf(stderr, "UDP ERROR (%d): %s\n", res, strerror(res)); 421 | } 422 | res = -1; 423 | } else { 424 | if (libsam3a_debug) fprintf(stderr, "UDP: %d bytes sent\n", res); 425 | } 426 | // 427 | close(fd); 428 | // 429 | return (res >= 0 ? 0 : -1); 430 | } 431 | 432 | 433 | int sam3audpSendTo (const char *hostname, int port, const void *buf, int 434 | bufSize, uint32_t *ip) { struct hostent *host = NULL; 435 | // 436 | if (buf == NULL || bufSize < 1) return -1; 437 | if (hostname == NULL || !hostname[0]) hostname = "localhost"; 438 | if (port < 1 || port > 65535) port = 7655; 439 | // 440 | host = gethostbyname(hostname); 441 | if (host == NULL || host->h_name == NULL || !host->h_name[0]) { 442 | if (libsam3a_debug) fprintf(stderr, "ERROR: can't resolve '%s'\n", 443 | hostname); return -1; 444 | } 445 | // 446 | if (ip != NULL) *ip = ((struct in_addr *)host->h_addr)->s_addr; 447 | return sam3audpSendToIP(((struct in_addr *)host->h_addr)->s_addr, port, buf, 448 | bufSize); 449 | } 450 | */ 451 | 452 | //////////////////////////////////////////////////////////////////////////////// 453 | typedef struct SAMFieldList { 454 | char *name; 455 | char *value; 456 | struct SAMFieldList *next; 457 | } SAMFieldList; 458 | 459 | static void sam3aFreeFieldList(SAMFieldList *list) { 460 | while (list != NULL) { 461 | SAMFieldList *c = list; 462 | // 463 | list = list->next; 464 | if (c->name != NULL) 465 | free(c->name); 466 | if (c->value != NULL) 467 | free(c->value); 468 | free(c); 469 | } 470 | } 471 | 472 | static void sam3aDumpFieldList(const SAMFieldList *list) { 473 | for (; list != NULL; list = list->next) { 474 | fprintf(stderr, "%s=[%s]\n", list->name, list->value); 475 | } 476 | } 477 | 478 | static const char *sam3aFindField(const SAMFieldList *list, const char *field) { 479 | if (list != NULL && field != NULL) { 480 | for (list = list->next; list != NULL; list = list->next) { 481 | if (list->name != NULL && strcmp(field, list->name) == 0) 482 | return list->value; 483 | } 484 | } 485 | return NULL; 486 | } 487 | 488 | static char *xstrdup(const char *s, int len) { 489 | if (len >= 0) { 490 | char *res = malloc(len + 1); 491 | // 492 | if (res != NULL) { 493 | if (len > 0) 494 | memcpy(res, s, len); 495 | res[len] = 0; 496 | } 497 | // 498 | return res; 499 | } 500 | // 501 | return NULL; 502 | } 503 | 504 | // returns NULL if there are no more tokens 505 | static inline const char *xstrtokend(const char *s) { 506 | while (*s && isspace(*s)) 507 | ++s; 508 | // 509 | if (*s) { 510 | char qch = 0; 511 | // 512 | while (*s) { 513 | if (*s == qch) { 514 | qch = 0; 515 | } else if (*s == '"') { 516 | qch = *s; 517 | } else if (qch) { 518 | if (*s == '\\' && s[1]) 519 | ++s; 520 | } else if (isspace(*s)) { 521 | break; 522 | } 523 | ++s; 524 | } 525 | } else { 526 | s = NULL; 527 | } 528 | // 529 | return s; 530 | } 531 | 532 | SAMFieldList *sam3aParseReply(const char *rep) { 533 | SAMFieldList *first = NULL, *last, *c; 534 | const char *p = rep, *e, *e1; 535 | // 536 | // first 2 words 537 | while (*p && isspace(*p)) 538 | ++p; 539 | if ((e = xstrtokend(p)) == NULL) 540 | return NULL; 541 | if ((e1 = xstrtokend(e)) == NULL) 542 | return NULL; 543 | // 544 | if ((first = last = c = malloc(sizeof(SAMFieldList))) == NULL) 545 | return NULL; 546 | c->next = NULL; 547 | c->name = c->value = NULL; 548 | if ((c->name = xstrdup(p, e - p)) == NULL) 549 | goto error; 550 | while (*e && isspace(*e)) 551 | ++e; 552 | if ((c->value = xstrdup(e, e1 - e)) == NULL) 553 | goto error; 554 | // 555 | p = e1; 556 | while (*p) { 557 | while (*p && isspace(*p)) 558 | ++p; 559 | if ((e = xstrtokend(p)) == NULL) 560 | break; // no more tokens 561 | // 562 | if (libsam3a_debug) 563 | fprintf(stderr, "<%s>\n", p); 564 | // 565 | if ((c = malloc(sizeof(SAMFieldList))) == NULL) 566 | return NULL; 567 | c->next = NULL; 568 | c->name = c->value = NULL; 569 | last->next = c; 570 | last = c; 571 | // 572 | if ((e1 = memchr(p, '=', e - p)) != NULL) { 573 | // key=value 574 | if ((c->name = xstrdup(p, e1 - p)) == NULL) 575 | goto error; 576 | if ((c->value = xstrdup(e1 + 1, e - e1 - 1)) == NULL) 577 | goto error; 578 | } else { 579 | // only key (there is no such replies in SAMv3, but... 580 | if ((c->name = xstrdup(p, e - p)) == NULL) 581 | goto error; 582 | if ((c->value = strdup("")) == NULL) 583 | goto error; 584 | } 585 | p = e; 586 | } 587 | // 588 | if (libsam3a_debug) 589 | sam3aDumpFieldList(first); 590 | // 591 | return first; 592 | error: 593 | sam3aFreeFieldList(first); 594 | return NULL; 595 | } 596 | 597 | // example: 598 | // r0: 'HELLO' 599 | // r1: 'REPLY' 600 | // field: NULL or 'RESULT' 601 | // VALUE: NULL or 'OK' 602 | // returns bool 603 | int sam3aIsGoodReply(const SAMFieldList *list, const char *r0, const char *r1, 604 | const char *field, const char *value) { 605 | if (list != NULL && list->name != NULL && list->value != NULL) { 606 | if (r0 != NULL && strcmp(r0, list->name) != 0) 607 | return 0; 608 | if (r1 != NULL && strcmp(r1, list->value) != 0) 609 | return 0; 610 | if (field != NULL) { 611 | for (list = list->next; list != NULL; list = list->next) { 612 | if (list->name == NULL || list->value == NULL) 613 | return 0; // invalid list, heh 614 | if (strcmp(field, list->name) == 0) { 615 | if (value != NULL && strcmp(value, list->value) != 0) 616 | return 0; 617 | return 1; 618 | } 619 | } 620 | } 621 | return 1; 622 | } 623 | return 0; 624 | } 625 | 626 | // NULL: error; else: list of fields 627 | // first item is always 2-word reply, with first word in name and second in 628 | // value 629 | /* 630 | SAMFieldList *sam3aReadReply (int fd) { 631 | char rep[2048]; // should be enough for any reply 632 | // 633 | if (sam3atcpReceiveStr(fd, rep, sizeof(rep)) < 0) return NULL; 634 | if (libsam3a_debug) fprintf(stderr, "SAM REPLY: [%s]\n", rep); 635 | return sam3aParseReply(rep); 636 | } 637 | */ 638 | 639 | //////////////////////////////////////////////////////////////////////////////// 640 | // by Bob Jenkins 641 | // public domain 642 | // http://burtleburtle.net/bob/rand/smallprng.html 643 | // 644 | //////////////////////////////////////////////////////////////////////////////// 645 | typedef struct { 646 | uint32_t a, b, c, d; 647 | } BJRandCtx; 648 | 649 | #define BJPRNG_ROT(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) 650 | 651 | static uint32_t bjprngRand(BJRandCtx *x) { 652 | uint32_t e; 653 | /* original: 654 | e = x->a-BJPRNG_ROT(x->b, 27); 655 | x->a = x->b^BJPRNG_ROT(x->c, 17); 656 | x->b = x->c+x->d; 657 | x->c = x->d+e; 658 | x->d = e+x->a; 659 | */ 660 | /* better, but slower at least in idiotic m$vc */ 661 | e = x->a - BJPRNG_ROT(x->b, 23); 662 | x->a = x->b ^ BJPRNG_ROT(x->c, 16); 663 | x->b = x->c + BJPRNG_ROT(x->d, 11); 664 | x->c = x->d + e; 665 | x->d = e + x->a; 666 | // 667 | return x->d; 668 | } 669 | 670 | static void bjprngInit(BJRandCtx *x, uint32_t seed) { 671 | x->a = 0xf1ea5eed; 672 | x->b = x->c = x->d = seed; 673 | for (int i = 0; i < 20; ++i) 674 | bjprngRand(x); 675 | } 676 | 677 | static inline uint32_t hashint(uint32_t a) { 678 | a -= (a << 6); 679 | a ^= (a >> 17); 680 | a -= (a << 9); 681 | a ^= (a << 4); 682 | a -= (a << 3); 683 | a ^= (a << 10); 684 | a ^= (a >> 15); 685 | return a; 686 | } 687 | 688 | static uint32_t genSeed(void) { 689 | volatile uint32_t seed = 1; 690 | uint32_t res; 691 | #ifndef WIN32 692 | #ifndef __APPLE__ 693 | struct sysinfo sy; 694 | pid_t pid = getpid(); 695 | // 696 | sysinfo(&sy); 697 | res = hashint((uint32_t)pid) ^ hashint((uint32_t)time(NULL)) ^ 698 | hashint((uint32_t)sy.sharedram) ^ hashint((uint32_t)sy.bufferram) ^ 699 | hashint((uint32_t)sy.uptime); 700 | #else 701 | res = hashint((uint32_t)getpid()) ^ 702 | hashint((uint32_t)TickCount()); 703 | #endif 704 | #else 705 | res = hashint((uint32_t)GetCurrentProcessId()) ^ 706 | hashint((uint32_t)GetTickCount()); 707 | #endif 708 | res += __sync_fetch_and_add(&seed, 1); 709 | // 710 | return hashint(res); 711 | } 712 | 713 | //////////////////////////////////////////////////////////////////////////////// 714 | int sam3aGenChannelName(char *dest, int minlen, int maxlen) { 715 | BJRandCtx rc; 716 | int len; 717 | // 718 | if (dest == NULL || minlen < 1 || maxlen < minlen || minlen > 65536 || 719 | maxlen > 65536) 720 | return -1; 721 | bjprngInit(&rc, genSeed()); 722 | len = minlen + (bjprngRand(&rc) % (maxlen - minlen + 1)); 723 | while (len-- > 0) { 724 | int ch = bjprngRand(&rc) % 64; 725 | // 726 | if (ch >= 0 && ch < 10) 727 | ch += '0'; 728 | else if (ch >= 10 && ch < 36) 729 | ch += 'A' - 10; 730 | else if (ch >= 36 && ch < 62) 731 | ch += 'a' - 36; 732 | else if (ch == 62) 733 | ch = '-'; 734 | else if (ch == 63) 735 | ch = '_'; 736 | else if (ch > 64) 737 | abort(); 738 | *dest++ = ch; 739 | } 740 | *dest++ = 0; 741 | return 0; 742 | } 743 | 744 | //////////////////////////////////////////////////////////////////////////////// 745 | int sam3aIsActiveSession(const Sam3ASession *ses) { 746 | return (ses != NULL && ses->fd >= 0 && !ses->cancelled); 747 | } 748 | 749 | int sam3aIsActiveConnection(const Sam3AConnection *conn) { 750 | return (conn != NULL && conn->fd >= 0 && !conn->cancelled); 751 | } 752 | 753 | //////////////////////////////////////////////////////////////////////////////// 754 | static inline void strcpyerrs(Sam3ASession *ses, const char *errstr) { 755 | // memset(ses->error, 0, sizeof(ses->error)); 756 | ses->error[sizeof(ses->error) - 1] = 0; 757 | if (errstr != NULL) 758 | strncpy(ses->error, errstr, sizeof(ses->error) - 1); 759 | } 760 | 761 | static inline void strcpyerrc(Sam3AConnection *conn, const char *errstr) { 762 | // memset(conn->error, 0, sizeof(conn->error)); 763 | conn->error[sizeof(conn->error) - 1] = 0; 764 | if (errstr != NULL) 765 | strncpy(conn->error, errstr, sizeof(conn->error) - 1); 766 | } 767 | 768 | static void connDisconnect(Sam3AConnection *conn) { 769 | conn->cbAIOProcessorR = conn->cbAIOProcessorW = NULL; 770 | if (conn->aio.data != NULL) { 771 | free(conn->aio.data); 772 | conn->aio.data = NULL; 773 | } 774 | if (!conn->cancelled && conn->fd >= 0) { 775 | conn->cancelled = 1; 776 | shutdown(conn->fd, SHUT_RDWR); 777 | if (conn->callDisconnectCB && conn->cb.cbDisconnected != NULL) 778 | conn->cb.cbDisconnected(conn); 779 | } 780 | } 781 | 782 | static void sesDisconnect(Sam3ASession *ses) { 783 | ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; 784 | if (ses->aio.data != NULL) { 785 | free(ses->aio.data); 786 | ses->aio.data = NULL; 787 | } 788 | if (!ses->cancelled && ses->fd >= 0) { 789 | ses->cancelled = 1; 790 | shutdown(ses->fd, SHUT_RDWR); 791 | for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) 792 | connDisconnect(c); 793 | if (ses->callDisconnectCB && ses->cb.cbDisconnected != NULL) 794 | ses->cb.cbDisconnected(ses); 795 | } 796 | } 797 | 798 | static void sesError(Sam3ASession *ses, const char *errstr) { 799 | if (errstr == NULL || !errstr[0]) 800 | errstr = "I2P_ERROR"; 801 | strcpyerrs(ses, errstr); 802 | if (ses->cb.cbError != NULL) 803 | ses->cb.cbError(ses); 804 | sesDisconnect(ses); 805 | } 806 | 807 | static void connError(Sam3AConnection *conn, const char *errstr) { 808 | if (errstr == NULL || !errstr[0]) 809 | errstr = "I2P_ERROR"; 810 | strcpyerrc(conn, errstr); 811 | if (conn->cb.cbError != NULL) 812 | conn->cb.cbError(conn); 813 | connDisconnect(conn); 814 | } 815 | 816 | //////////////////////////////////////////////////////////////////////////////// 817 | static int aioSender(int fd, Sam3AIO *aio) { 818 | int wr = sam3aSendBytes(fd, aio->data + aio->dataPos, 819 | aio->dataUsed - aio->dataPos); 820 | // 821 | if (wr < 0) 822 | return -1; 823 | aio->dataPos += wr; 824 | return 0; 825 | } 826 | 827 | // dataUsed: max line size (with '\n') 828 | // dataSize: must be at least (dataUsed+1) 829 | static int aioLineReader(int fd, Sam3AIO *aio) { 830 | // 831 | for (;;) { 832 | int av = sam3aBytesAvail(fd), rd; 833 | // 834 | if (av < 0) 835 | return -1; 836 | if (av == 0) 837 | return 0; // do nothing 838 | if (aio->dataPos >= aio->dataUsed - 1) 839 | return -1; // line too long 840 | if ((rd = (aio->dataUsed - 1) - aio->dataPos) > av) 841 | rd = av; 842 | if ((rd = recv(fd, aio->data + aio->dataPos, rd, MSG_PEEK)) < 0) { 843 | if (errno == EINTR) 844 | continue; 845 | return -1; 846 | } 847 | if (rd == 0) 848 | return 0; // do nothing 849 | // now look for '\n' 850 | for (int f = aio->dataPos; f < aio->dataPos + rd; ++f) { 851 | if (aio->data[f] == '\n') { 852 | // got it! 853 | if (recv(fd, aio->data + aio->dataPos, f - aio->dataPos + 1, 0) != 854 | f + 1) 855 | return -1; // the thing that should not be 856 | aio->data[f] = 0; // convert to asciiz 857 | aio->dataUsed = aio->dataPos = f; // length 858 | return 1; // '\n' found! 859 | } 860 | if (!aio->data[f]) 861 | return -1; // there should not be zero bytes 862 | } 863 | // no '\n' found 864 | if (recv(fd, aio->data + aio->dataPos, rd, 0) != rd) 865 | return -1; // the thing that should not be 866 | aio->dataPos += rd; 867 | } 868 | } 869 | 870 | //////////////////////////////////////////////////////////////////////////////// 871 | static void aioSesCmdReplyReader(Sam3ASession *ses) { 872 | int res = aioLineReader(ses->fd, &ses->aio); 873 | // 874 | if (res < 0) { 875 | sesError(ses, "IO_ERROR"); 876 | return; 877 | } 878 | if (res > 0) { 879 | // we got full line 880 | if (libsam3a_debug) 881 | fprintf(stderr, "CMDREPLY: %s\n", ses->aio.data); 882 | if (ses->aio.cbReplyCheckSes != NULL) 883 | ses->aio.cbReplyCheckSes(ses); 884 | } 885 | } 886 | 887 | static void aioSesCmdSender(Sam3ASession *ses) { 888 | if (ses->aio.dataPos < ses->aio.dataUsed) { 889 | if (aioSender(ses->fd, &ses->aio) < 0) { 890 | sesError(ses, "IO_ERROR"); 891 | return; 892 | } 893 | } 894 | // 895 | if (ses->aio.dataPos == ses->aio.dataUsed) { 896 | // hello sent, now wait for reply 897 | // 2048 bytes of reply line should be enough 898 | if (ses->aio.dataSize < 2049) { 899 | char *n = realloc(ses->aio.data, 2049); 900 | // 901 | if (n == NULL) { 902 | sesError(ses, "MEMORY_ERROR"); 903 | return; 904 | } 905 | ses->aio.data = n; 906 | ses->aio.dataSize = 2049; 907 | } 908 | ses->aio.dataUsed = 2048; 909 | ses->aio.dataPos = 0; 910 | ses->cbAIOProcessorR = aioSesCmdReplyReader; 911 | ses->cbAIOProcessorW = NULL; 912 | } 913 | } 914 | 915 | static __attribute__((format(printf, 3, 4))) int 916 | aioSesSendCmdWaitReply(Sam3ASession *ses, void (*cbCheck)(Sam3ASession *ses), 917 | const char *fmt, ...) { 918 | va_list ap; 919 | char *str; 920 | int len; 921 | // 922 | va_start(ap, fmt); 923 | str = sam3PrintfVA(&len, fmt, ap); 924 | va_end(ap); 925 | // 926 | if (str == NULL) 927 | return -1; 928 | if (ses->aio.data != NULL) 929 | free(ses->aio.data); 930 | ses->aio.data = str; 931 | ses->aio.dataUsed = len; 932 | ses->aio.dataSize = len + 1; 933 | ses->aio.dataPos = 0; 934 | ses->aio.cbReplyCheckSes = cbCheck; 935 | ses->cbAIOProcessorR = NULL; 936 | ses->cbAIOProcessorW = aioSesCmdSender; 937 | // 938 | if (libsam3a_debug) 939 | fprintf(stderr, "CMD: %s", str); 940 | return 0; 941 | } 942 | 943 | //////////////////////////////////////////////////////////////////////////////// 944 | static void aioSesHelloChecker(Sam3ASession *ses) { 945 | SAMFieldList *rep = sam3aParseReply(ses->aio.data); 946 | // 947 | if (rep != NULL && sam3aIsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK") && 948 | sam3aIsGoodReply(rep, NULL, NULL, "VERSION", "3.0")) { 949 | ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; 950 | sam3aFreeFieldList(rep); 951 | if (ses->aio.udata != NULL) { 952 | void (*cbComplete)(Sam3ASession * ses) = ses->aio.udata; 953 | // 954 | cbComplete(ses); 955 | } 956 | } else { 957 | sam3aFreeFieldList(rep); 958 | sesError(ses, NULL); 959 | } 960 | } 961 | 962 | static int sam3aSesStartHandshake(Sam3ASession *ses, 963 | void (*cbComplete)(Sam3ASession *ses)) { 964 | if (cbComplete != NULL) 965 | ses->aio.udata = cbComplete; 966 | if (aioSesSendCmdWaitReply(ses, aioSesHelloChecker, "%s\n", 967 | "HELLO VERSION MIN=3.0 MAX=3.0") < 0) 968 | return -1; 969 | return 0; 970 | } 971 | 972 | //////////////////////////////////////////////////////////////////////////////// 973 | static void aioSesNameMeChecker(Sam3ASession *ses) { 974 | SAMFieldList *rep = sam3aParseReply(ses->aio.data); 975 | const char *v = NULL; 976 | // 977 | if (rep == NULL) { 978 | sesError(ses, NULL); 979 | return; 980 | } 981 | if (!sam3aIsGoodReply(rep, "NAMING", "REPLY", "RESULT", "OK") || 982 | (v = sam3aFindField(rep, "VALUE")) == NULL || 983 | strlen(v) != SAM3A_PUBKEY_SIZE) { 984 | // if (libsam3a_debug) fprintf(stderr, "sam3aCreateSession: invalid NAMING 985 | // reply (%d)...\n", (v != NULL ? strlen(v) : -1)); 986 | if ((v = sam3aFindField(rep, "RESULT")) != NULL && strcmp(v, "OK") == 0) 987 | v = NULL; 988 | sesError(ses, v); 989 | sam3aFreeFieldList(rep); 990 | return; 991 | } 992 | strcpy(ses->pubkey, v); 993 | sam3aFreeFieldList(rep); 994 | // 995 | ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; 996 | ses->callDisconnectCB = 1; 997 | if (ses->cb.cbCreated != NULL) 998 | ses->cb.cbCreated(ses); 999 | } 1000 | 1001 | static void aioSesCreateChecker(Sam3ASession *ses) { 1002 | SAMFieldList *rep = sam3aParseReply(ses->aio.data); 1003 | const char *v; 1004 | // 1005 | if (rep == NULL) { 1006 | sesError(ses, NULL); 1007 | return; 1008 | } 1009 | if (!sam3aIsGoodReply(rep, "SESSION", "STATUS", "RESULT", "OK") || 1010 | (v = sam3aFindField(rep, "DESTINATION")) == NULL || 1011 | strlen(v) != SAM3A_PRIVKEY_SIZE) { 1012 | sam3aFreeFieldList(rep); 1013 | if ((v = sam3aFindField(rep, "RESULT")) != NULL && strcmp(v, "OK") == 0) 1014 | v = NULL; 1015 | sesError(ses, v); 1016 | return; 1017 | } 1018 | // ok 1019 | // fprintf(stderr, "\nPK: %s\n", v); 1020 | strcpy(ses->privkey, v); 1021 | sam3aFreeFieldList(rep); 1022 | // get our public key 1023 | if (aioSesSendCmdWaitReply(ses, aioSesNameMeChecker, "%s\n", 1024 | "NAMING LOOKUP NAME=ME") < 0) { 1025 | sesError(ses, "MEMORY_ERROR"); 1026 | } 1027 | } 1028 | 1029 | // handshake for SESSION CREATE complete 1030 | static void aioSesHandshacked(Sam3ASession *ses) { 1031 | static const char *typenames[3] = {"RAW", "DATAGRAM", "STREAM"}; 1032 | // 1033 | if (aioSesSendCmdWaitReply( 1034 | ses, aioSesCreateChecker, 1035 | "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s%s%s\n", 1036 | typenames[(int)ses->type], ses->channel, ses->privkey, 1037 | (ses->params != NULL ? " " : ""), 1038 | (ses->params != NULL ? ses->params : "")) < 0) { 1039 | sesError(ses, "MEMORY_ERROR"); 1040 | } 1041 | } 1042 | 1043 | static void aioSesConnected(Sam3ASession *ses) { 1044 | int res; 1045 | socklen_t len = sizeof(res); 1046 | // 1047 | if (getsockopt(ses->fd, SOL_SOCKET, SO_ERROR, &res, &len) == 0 && res == 0) { 1048 | // ok, connected 1049 | if (sam3aSesStartHandshake(ses, NULL) < 0) 1050 | sesError(ses, NULL); 1051 | } else { 1052 | // connection error 1053 | sesError(ses, "CONNECTION_ERROR"); 1054 | } 1055 | } 1056 | 1057 | //////////////////////////////////////////////////////////////////////////////// 1058 | int sam3aCreateSessionEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, 1059 | const char *hostname, int port, const char *privkey, 1060 | Sam3ASessionType type, const char *params, 1061 | int timeoutms) { 1062 | if (ses != NULL) { 1063 | // int complete = 0; 1064 | // 1065 | memset(ses, 0, sizeof(Sam3ASession)); 1066 | ses->fd = -1; 1067 | if (cb != NULL) 1068 | ses->cb = *cb; 1069 | if (hostname == NULL || !hostname[0]) 1070 | hostname = "127.0.0.1"; 1071 | if (port < 0 || port > 65535) 1072 | goto error; 1073 | if (privkey != NULL && strlen(privkey) != SAM3A_PRIVKEY_SIZE) 1074 | goto error; 1075 | if ((int)type < 0 || (int)type > 2) 1076 | goto error; 1077 | if (privkey == NULL) 1078 | privkey = "TRANSIENT"; 1079 | strcpy(ses->privkey, privkey); 1080 | if (params != NULL && (ses->params = strdup(params)) == NULL) 1081 | goto error; 1082 | ses->timeoutms = timeoutms; 1083 | // 1084 | if (!port) 1085 | port = DEFAULT_TCP_PORT; 1086 | ses->type = type; 1087 | ses->port = (type == SAM3A_SESSION_STREAM ? port : DEFAULT_UDP_PORT); 1088 | if ((ses->ip = sam3aResolveHost(hostname)) == 0) 1089 | goto error; 1090 | sam3aGenChannelName(ses->channel, 32, 64); 1091 | if (libsam3a_debug) 1092 | fprintf(stderr, "sam3aCreateSession: channel=[%s]\n", ses->channel); 1093 | // 1094 | ses->aio.udata = aioSesHandshacked; 1095 | ses->cbAIOProcessorW = aioSesConnected; 1096 | if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) 1097 | goto error; 1098 | /* 1099 | if (complete) { 1100 | ses->cbAIOProcessorW(ses); 1101 | if (!sam3aIsActiveSession(ses)) return -1; 1102 | } 1103 | */ 1104 | // 1105 | return 0; // ok, connection process initiated 1106 | error: 1107 | if (ses->fd >= 0) 1108 | sam3aDisconnect(ses->fd); 1109 | if (ses->params != NULL) 1110 | free(ses->params); 1111 | memset(ses, 0, sizeof(Sam3ASession)); 1112 | ses->fd = -1; 1113 | } 1114 | return -1; 1115 | } 1116 | 1117 | //////////////////////////////////////////////////////////////////////////////// 1118 | int sam3aCancelSession(Sam3ASession *ses) { 1119 | if (ses != NULL) { 1120 | sesDisconnect(ses); 1121 | return 0; 1122 | } 1123 | return -1; 1124 | } 1125 | 1126 | int sam3aCloseSession(Sam3ASession *ses) { 1127 | if (ses != NULL) { 1128 | sam3aCancelSession(ses); 1129 | while (ses->connlist != NULL) 1130 | sam3aCloseConnection(ses->connlist); 1131 | if (ses->cb.cbDestroy != NULL) 1132 | ses->cb.cbDestroy(ses); 1133 | if (ses->params != NULL) { 1134 | free(ses->params); 1135 | ses->params = NULL; 1136 | } 1137 | memset(ses, 0, sizeof(Sam3ASession)); 1138 | } 1139 | return -1; 1140 | } 1141 | 1142 | //////////////////////////////////////////////////////////////////////////////// 1143 | static void aioSesKeyGenChecker(Sam3ASession *ses) { 1144 | SAMFieldList *rep = sam3aParseReply(ses->aio.data); 1145 | // 1146 | if (rep == NULL) { 1147 | sesError(ses, NULL); 1148 | return; 1149 | } 1150 | if (sam3aIsGoodReply(rep, "DEST", "REPLY", NULL, NULL)) { 1151 | const char *pub = sam3aFindField(rep, "PUB"), 1152 | *priv = sam3aFindField(rep, "PRIV"); 1153 | // 1154 | if (pub != NULL && strlen(pub) == SAM3A_PUBKEY_SIZE && priv != NULL && 1155 | strlen(priv) == SAM3A_PRIVKEY_SIZE) { 1156 | strcpy(ses->pubkey, pub); 1157 | strcpy(ses->privkey, priv); 1158 | sam3aFreeFieldList(rep); 1159 | if (ses->cb.cbCreated != NULL) 1160 | ses->cb.cbCreated(ses); 1161 | sam3aCancelSession(ses); 1162 | return; 1163 | } 1164 | } 1165 | sam3aFreeFieldList(rep); 1166 | sesError(ses, NULL); 1167 | } 1168 | 1169 | // handshake for SESSION CREATE complete 1170 | static void aioSesKeyGenHandshacked(Sam3ASession *ses) { 1171 | if (aioSesSendCmdWaitReply(ses, aioSesKeyGenChecker, "%s\n", 1172 | "DEST GENERATE") < 0) { 1173 | sesError(ses, "MEMORY_ERROR"); 1174 | } 1175 | } 1176 | 1177 | int sam3aGenerateKeysEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, 1178 | const char *hostname, int port, int timeoutms) { 1179 | if (ses != NULL) { 1180 | memset(ses, 0, sizeof(Sam3ASession)); 1181 | ses->fd = -1; 1182 | if (cb != NULL) 1183 | ses->cb = *cb; 1184 | if (hostname == NULL || !hostname[0]) 1185 | hostname = "127.0.0.1"; 1186 | if (port < 0 || port > 65535) 1187 | goto error; 1188 | ses->timeoutms = timeoutms; 1189 | // 1190 | if (!port) 1191 | port = DEFAULT_TCP_PORT; 1192 | ses->port = port; 1193 | if ((ses->ip = sam3aResolveHost(hostname)) == 0) 1194 | goto error; 1195 | // 1196 | ses->aio.udata = aioSesKeyGenHandshacked; 1197 | ses->cbAIOProcessorW = aioSesConnected; 1198 | if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) 1199 | goto error; 1200 | // 1201 | return 0; // ok, connection process initiated 1202 | error: 1203 | if (ses->fd >= 0) 1204 | sam3aDisconnect(ses->fd); 1205 | if (ses->params != NULL) 1206 | free(ses->params); 1207 | memset(ses, 0, sizeof(Sam3ASession)); 1208 | ses->fd = -1; 1209 | } 1210 | return -1; 1211 | } 1212 | 1213 | //////////////////////////////////////////////////////////////////////////////// 1214 | static void aioSesNameResChecker(Sam3ASession *ses) { 1215 | SAMFieldList *rep = sam3aParseReply(ses->aio.data); 1216 | // 1217 | if (rep == NULL) { 1218 | sesError(ses, NULL); 1219 | return; 1220 | } 1221 | if (sam3aIsGoodReply(rep, "NAMING", "REPLY", "RESULT", NULL)) { 1222 | const char *rs = sam3aFindField(rep, "RESULT"), 1223 | *pub = sam3aFindField(rep, "VALUE"); 1224 | // 1225 | if (strcmp(rs, "OK") == 0) { 1226 | if (pub != NULL && strlen(pub) == SAM3A_PUBKEY_SIZE) { 1227 | strcpy(ses->destkey, pub); 1228 | sam3aFreeFieldList(rep); 1229 | if (ses->cb.cbCreated != NULL) 1230 | ses->cb.cbCreated(ses); 1231 | sam3aCancelSession(ses); 1232 | return; 1233 | } 1234 | sam3aFreeFieldList(rep); 1235 | sesError(ses, NULL); 1236 | } else { 1237 | sesError(ses, rs); 1238 | sam3aFreeFieldList(rep); 1239 | } 1240 | } 1241 | } 1242 | 1243 | // handshake for SESSION CREATE complete 1244 | static void aioSesNameResHandshacked(Sam3ASession *ses) { 1245 | if (aioSesSendCmdWaitReply(ses, aioSesNameResChecker, 1246 | "NAMING LOOKUP NAME=%s\n", ses->params) < 0) { 1247 | sesError(ses, "MEMORY_ERROR"); 1248 | } 1249 | } 1250 | 1251 | int sam3aNameLookupEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, 1252 | const char *hostname, int port, const char *name, 1253 | int timeoutms) { 1254 | if (ses != NULL) { 1255 | memset(ses, 0, sizeof(Sam3ASession)); 1256 | ses->fd = -1; 1257 | if (cb != NULL) 1258 | ses->cb = *cb; 1259 | if (name == NULL || !name[0] || 1260 | (name[0] && toupper(name[0]) == 'M' && name[1] && 1261 | toupper(name[1]) == 'E' && (!name[2] || isspace(name[2])))) 1262 | goto error; 1263 | if (hostname == NULL || !hostname[0]) 1264 | hostname = "127.0.0.1"; 1265 | if (port < 0 || port > 65535) 1266 | goto error; 1267 | if ((ses->params = strdup(name)) == NULL) 1268 | goto error; 1269 | ses->timeoutms = timeoutms; 1270 | // 1271 | if (!port) 1272 | port = DEFAULT_TCP_PORT; 1273 | ses->port = port; 1274 | if ((ses->ip = sam3aResolveHost(hostname)) == 0) 1275 | goto error; 1276 | // 1277 | ses->aio.udata = aioSesNameResHandshacked; 1278 | ses->cbAIOProcessorW = aioSesConnected; 1279 | if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) 1280 | goto error; 1281 | // 1282 | return 0; // ok, connection process initiated 1283 | error: 1284 | if (ses->fd >= 0) 1285 | sam3aDisconnect(ses->fd); 1286 | if (ses->params != NULL) 1287 | free(ses->params); 1288 | memset(ses, 0, sizeof(Sam3ASession)); 1289 | ses->fd = -1; 1290 | } 1291 | return -1; 1292 | } 1293 | 1294 | //////////////////////////////////////////////////////////////////////////////// 1295 | static void aioConnCmdReplyReader(Sam3AConnection *conn) { 1296 | int res = aioLineReader(conn->fd, &conn->aio); 1297 | // 1298 | if (res < 0) { 1299 | connError(conn, "IO_ERROR"); 1300 | return; 1301 | } 1302 | if (res > 0) { 1303 | // we got full line 1304 | if (libsam3a_debug) 1305 | fprintf(stderr, "CMDREPLY: %s\n", conn->aio.data); 1306 | if (conn->aio.cbReplyCheckConn != NULL) 1307 | conn->aio.cbReplyCheckConn(conn); 1308 | } 1309 | } 1310 | 1311 | static void aioConnCmdSender(Sam3AConnection *conn) { 1312 | if (conn->aio.dataPos < conn->aio.dataUsed) { 1313 | if (aioSender(conn->fd, &conn->aio) < 0) { 1314 | connError(conn, "IO_ERROR"); 1315 | return; 1316 | } 1317 | } 1318 | // 1319 | if (conn->aio.dataPos == conn->aio.dataUsed) { 1320 | // hello sent, now wait for reply 1321 | // 2048 bytes of reply line should be enough 1322 | if (conn->aio.dataSize < 2049) { 1323 | char *n = realloc(conn->aio.data, 2049); 1324 | // 1325 | if (n == NULL) { 1326 | connError(conn, "MEMORY_ERROR"); 1327 | return; 1328 | } 1329 | conn->aio.data = n; 1330 | conn->aio.dataSize = 2049; 1331 | } 1332 | conn->aio.dataUsed = 2048; 1333 | conn->aio.dataPos = 0; 1334 | conn->cbAIOProcessorR = aioConnCmdReplyReader; 1335 | conn->cbAIOProcessorW = NULL; 1336 | } 1337 | } 1338 | 1339 | static __attribute__((format(printf, 3, 4))) int 1340 | aioConnSendCmdWaitReply(Sam3AConnection *conn, 1341 | void (*cbCheck)(Sam3AConnection *conn), const char *fmt, 1342 | ...) { 1343 | va_list ap; 1344 | char *str; 1345 | int len; 1346 | // 1347 | va_start(ap, fmt); 1348 | str = sam3PrintfVA(&len, fmt, ap); 1349 | va_end(ap); 1350 | // 1351 | if (str == NULL) 1352 | return -1; 1353 | if (conn->aio.data != NULL) 1354 | free(conn->aio.data); 1355 | conn->aio.data = str; 1356 | conn->aio.dataUsed = len; 1357 | conn->aio.dataSize = len + 1; 1358 | conn->aio.dataPos = 0; 1359 | conn->aio.cbReplyCheckConn = cbCheck; 1360 | conn->cbAIOProcessorR = NULL; 1361 | conn->cbAIOProcessorW = aioConnCmdSender; 1362 | // 1363 | if (libsam3a_debug) 1364 | fprintf(stderr, "CMD: %s", str); 1365 | return 0; 1366 | } 1367 | 1368 | //////////////////////////////////////////////////////////////////////////////// 1369 | static void aioConnDataReader(Sam3AConnection *conn) { 1370 | char *buf = NULL; 1371 | int bufsz = 0; 1372 | // 1373 | while (sam3aIsActiveConnection(conn)) { 1374 | int av = sam3aBytesAvail(conn->fd), rd; 1375 | // 1376 | if (av < 0) { 1377 | if (buf != NULL) 1378 | free(buf); 1379 | connError(conn, "IO_ERROR"); 1380 | return; 1381 | } 1382 | if (av == 0) 1383 | av = 1; 1384 | if (bufsz < av) { 1385 | char *n = realloc(buf, av + 1); 1386 | // 1387 | if (n == NULL) { 1388 | if (buf != NULL) 1389 | free(buf); 1390 | connError(conn, "IO_ERROR"); 1391 | return; 1392 | } 1393 | buf = n; 1394 | bufsz = av; 1395 | } 1396 | memset(buf, 0, av + 1); 1397 | // 1398 | rd = recv(conn->fd, buf, av, 0); 1399 | // 1400 | if (rd < 0) { 1401 | if (errno == EINTR) 1402 | continue; // interrupted by signal 1403 | if (errno == EAGAIN || errno == EWOULDBLOCK) 1404 | break; // no more data 1405 | free(buf); 1406 | connError(conn, "IO_ERROR"); 1407 | return; 1408 | } 1409 | // 1410 | if (rd == 0) { 1411 | // connection closed 1412 | free(buf); 1413 | connDisconnect(conn); 1414 | return; 1415 | } 1416 | // 1417 | if (conn->cb.cbRead != NULL) 1418 | conn->cb.cbRead(conn, buf, rd); 1419 | } 1420 | free(buf); 1421 | } 1422 | 1423 | static void aioConnDataWriter(Sam3AConnection *conn) { 1424 | if (!sam3aIsActiveConnection(conn)) { 1425 | conn->aio.dataPos = conn->aio.dataUsed = 0; 1426 | return; 1427 | } 1428 | // 1429 | if (conn->aio.dataPos >= conn->aio.dataUsed) { 1430 | conn->aio.dataPos = conn->aio.dataUsed = 0; 1431 | return; 1432 | } 1433 | // 1434 | while (sam3aIsActiveConnection(conn) && 1435 | conn->aio.dataPos < conn->aio.dataUsed) { 1436 | int wr = sam3aSendBytes(conn->fd, conn->aio.data + conn->aio.dataPos, 1437 | conn->aio.dataUsed - conn->aio.dataPos); 1438 | // 1439 | if (wr < 0) { 1440 | connError(conn, "IO_ERROR"); 1441 | return; 1442 | } 1443 | if (wr == 0) 1444 | break; // can't write more bytes 1445 | conn->aio.dataPos += wr; 1446 | if (conn->aio.dataPos < conn->aio.dataUsed) { 1447 | memmove(conn->aio.data, conn->aio.data + conn->aio.dataPos, 1448 | conn->aio.dataUsed - conn->aio.dataPos); 1449 | conn->aio.dataUsed -= conn->aio.dataPos; 1450 | conn->aio.dataPos = 0; 1451 | } 1452 | } 1453 | // 1454 | if (conn->aio.dataPos >= conn->aio.dataUsed) { 1455 | conn->aio.dataPos = conn->aio.dataUsed = 0; 1456 | if (conn->cb.cbSent != NULL) 1457 | conn->cb.cbSent(conn); 1458 | if (conn->aio.dataSize > 8192) { 1459 | // shrink buffer 1460 | char *nn = realloc(conn->aio.data, 8192); 1461 | // 1462 | if (nn != NULL) { 1463 | conn->aio.data = nn; 1464 | conn->aio.dataSize = 8192; 1465 | } 1466 | } 1467 | } 1468 | } 1469 | 1470 | //////////////////////////////////////////////////////////////////////////////// 1471 | static void aioConnHelloChecker(Sam3AConnection *conn) { 1472 | SAMFieldList *rep = sam3aParseReply(conn->aio.data); 1473 | // 1474 | if (rep != NULL && sam3aIsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK") && 1475 | sam3aIsGoodReply(rep, NULL, NULL, "VERSION", "3.0")) { 1476 | conn->cbAIOProcessorR = conn->cbAIOProcessorW = NULL; 1477 | sam3aFreeFieldList(rep); 1478 | if (conn->aio.udata != NULL) { 1479 | void (*cbComplete)(Sam3AConnection * conn) = conn->aio.udata; 1480 | // 1481 | cbComplete(conn); 1482 | } 1483 | } else { 1484 | sam3aFreeFieldList(rep); 1485 | connError(conn, NULL); 1486 | } 1487 | } 1488 | 1489 | static int sam3aConnStartHandshake(Sam3AConnection *conn, 1490 | void (*cbComplete)(Sam3AConnection *conn)) { 1491 | if (cbComplete != NULL) 1492 | conn->aio.udata = cbComplete; 1493 | if (aioConnSendCmdWaitReply(conn, aioConnHelloChecker, "%s\n", 1494 | "HELLO VERSION MIN=3.0 MAX=3.0") < 0) 1495 | return -1; 1496 | return 0; 1497 | } 1498 | 1499 | static void aioConnConnected(Sam3AConnection *conn) { 1500 | int res; 1501 | socklen_t len = sizeof(res); 1502 | // 1503 | if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &res, &len) == 0 && res == 0) { 1504 | // ok, connected 1505 | if (sam3aConnStartHandshake(conn, NULL) < 0) 1506 | connError(conn, NULL); 1507 | } else { 1508 | // connection error 1509 | connError(conn, "CONNECTION_ERROR"); 1510 | } 1511 | } 1512 | 1513 | //////////////////////////////////////////////////////////////////////////////// 1514 | static void aioConnConnectChecker(Sam3AConnection *conn) { 1515 | SAMFieldList *rep = sam3aParseReply(conn->aio.data); 1516 | // 1517 | if (rep == NULL) { 1518 | connError(conn, NULL); 1519 | return; 1520 | } 1521 | if (!sam3aIsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { 1522 | const char *v = sam3aFindField(rep, "RESULT"); 1523 | // 1524 | connError(conn, v); 1525 | sam3aFreeFieldList(rep); 1526 | } else { 1527 | // no error 1528 | sam3aFreeFieldList(rep); 1529 | conn->callDisconnectCB = 1; 1530 | conn->cbAIOProcessorR = aioConnDataReader; 1531 | conn->cbAIOProcessorW = aioConnDataWriter; 1532 | conn->aio.dataPos = conn->aio.dataUsed = 0; 1533 | if (conn->cb.cbConnected != NULL) 1534 | conn->cb.cbConnected(conn); 1535 | // indicate that we are ready for new data 1536 | if (sam3aIsActiveConnection(conn) && conn->cb.cbSent != NULL) 1537 | conn->cb.cbSent(conn); 1538 | } 1539 | } 1540 | 1541 | // handshake for SESSION CREATE complete 1542 | static void aioConConnectHandshacked(Sam3AConnection *conn) { 1543 | if (aioConnSendCmdWaitReply(conn, aioConnConnectChecker, 1544 | "STREAM CONNECT ID=%s DESTINATION=%s\n", 1545 | conn->ses->channel, conn->destkey) < 0) { 1546 | connError(conn, "MEMORY_ERROR"); 1547 | } 1548 | } 1549 | 1550 | //////////////////////////////////////////////////////////////////////////////// 1551 | Sam3AConnection *sam3aStreamConnectEx(Sam3ASession *ses, 1552 | const Sam3AConnectionCallbacks *cb, 1553 | const char *destkey, int timeoutms) { 1554 | if (sam3aIsActiveSession(ses) && ses->type == SAM3A_SESSION_STREAM && 1555 | destkey != NULL && strlen(destkey) == SAM3A_PUBKEY_SIZE) { 1556 | Sam3AConnection *conn = calloc(1, sizeof(Sam3AConnection)); 1557 | // 1558 | if (conn == NULL) 1559 | return NULL; 1560 | if (cb != NULL) 1561 | conn->cb = *cb; 1562 | strcpy(conn->destkey, destkey); 1563 | conn->timeoutms = timeoutms; 1564 | // 1565 | conn->aio.udata = aioConConnectHandshacked; 1566 | conn->cbAIOProcessorW = aioConnConnected; 1567 | if ((conn->fd = sam3aConnect(ses->ip, ses->port, NULL)) < 0) 1568 | goto error; 1569 | // 1570 | conn->ses = ses; 1571 | conn->next = ses->connlist; 1572 | ses->connlist = conn; 1573 | return conn; // ok, connection process initiated 1574 | error: 1575 | if (conn->fd >= 0) 1576 | sam3aDisconnect(conn->fd); 1577 | memset(conn, 0, sizeof(Sam3AConnection)); 1578 | free(conn); 1579 | } 1580 | return NULL; 1581 | } 1582 | 1583 | //////////////////////////////////////////////////////////////////////////////// 1584 | static void aioConnAcceptCheckerA(Sam3AConnection *conn) { 1585 | SAMFieldList *rep = sam3aParseReply(conn->aio.data); 1586 | // 1587 | if (rep != NULL || strlen(conn->aio.data) != SAM3A_PUBKEY_SIZE || 1588 | !sam3aIsValidPubKey(conn->aio.data)) { 1589 | sam3aFreeFieldList(rep); 1590 | connError(conn, NULL); 1591 | return; 1592 | } 1593 | sam3aFreeFieldList(rep); 1594 | strcpy(conn->destkey, conn->aio.data); 1595 | conn->callDisconnectCB = 1; 1596 | conn->cbAIOProcessorR = aioConnDataReader; 1597 | conn->cbAIOProcessorW = aioConnDataWriter; 1598 | conn->aio.dataPos = conn->aio.dataUsed = 0; 1599 | if (conn->cb.cbAccepted != NULL) 1600 | conn->cb.cbAccepted(conn); 1601 | // indicate that we are ready for new data 1602 | if (sam3aIsActiveConnection(conn) && conn->cb.cbSent != NULL) 1603 | conn->cb.cbSent(conn); 1604 | } 1605 | 1606 | static void aioConnAcceptChecker(Sam3AConnection *conn) { 1607 | SAMFieldList *rep = sam3aParseReply(conn->aio.data); 1608 | // 1609 | if (rep == NULL) { 1610 | connError(conn, NULL); 1611 | return; 1612 | } 1613 | if (!sam3aIsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { 1614 | const char *v = sam3aFindField(rep, "RESULT"); 1615 | // 1616 | connError(conn, v); 1617 | sam3aFreeFieldList(rep); 1618 | } else { 1619 | // no error 1620 | sam3aFreeFieldList(rep); 1621 | // 2048 bytes of reply line should be enough 1622 | if (conn->aio.dataSize < 2049) { 1623 | char *n = realloc(conn->aio.data, 2049); 1624 | // 1625 | if (n == NULL) { 1626 | connError(conn, "MEMORY_ERROR"); 1627 | return; 1628 | } 1629 | conn->aio.data = n; 1630 | conn->aio.dataSize = 2049; 1631 | } 1632 | conn->aio.dataUsed = 2048; 1633 | conn->aio.dataPos = 0; 1634 | conn->cbAIOProcessorR = aioConnCmdReplyReader; 1635 | conn->cbAIOProcessorW = NULL; 1636 | conn->aio.cbReplyCheckConn = aioConnAcceptCheckerA; 1637 | } 1638 | } 1639 | 1640 | // handshake for SESSION CREATE complete 1641 | static void aioConAcceptHandshacked(Sam3AConnection *conn) { 1642 | if (aioConnSendCmdWaitReply(conn, aioConnAcceptChecker, 1643 | "STREAM ACCEPT ID=%s\n", 1644 | conn->ses->channel) < 0) { 1645 | connError(conn, "MEMORY_ERROR"); 1646 | } 1647 | } 1648 | 1649 | //////////////////////////////////////////////////////////////////////////////// 1650 | Sam3AConnection *sam3aStreamAcceptEx(Sam3ASession *ses, 1651 | const Sam3AConnectionCallbacks *cb, 1652 | int timeoutms) { 1653 | if (sam3aIsActiveSession(ses) && ses->type == SAM3A_SESSION_STREAM) { 1654 | Sam3AConnection *conn = calloc(1, sizeof(Sam3AConnection)); 1655 | // 1656 | if (conn == NULL) 1657 | return NULL; 1658 | if (cb != NULL) 1659 | conn->cb = *cb; 1660 | conn->timeoutms = timeoutms; 1661 | // 1662 | conn->aio.udata = aioConAcceptHandshacked; 1663 | conn->cbAIOProcessorW = aioConnConnected; 1664 | if ((conn->fd = sam3aConnect(ses->ip, ses->port, NULL)) < 0) 1665 | goto error; 1666 | // 1667 | conn->ses = ses; 1668 | conn->next = ses->connlist; 1669 | ses->connlist = conn; 1670 | return conn; // ok, connection process initiated 1671 | error: 1672 | if (conn->fd >= 0) 1673 | sam3aDisconnect(conn->fd); 1674 | memset(conn, 0, sizeof(Sam3AConnection)); 1675 | free(conn); 1676 | } 1677 | return NULL; 1678 | } 1679 | 1680 | //////////////////////////////////////////////////////////////////////////////// 1681 | int sam3aSend(Sam3AConnection *conn, const void *data, int datasize) { 1682 | if (datasize == -1) 1683 | datasize = (data != NULL ? strlen((const char *)data) : 0); 1684 | // 1685 | if (sam3aIsActiveConnection(conn) && conn->callDisconnectCB && 1686 | conn->cbAIOProcessorW != NULL && 1687 | ((datasize > 0 && data != NULL) || datasize == 0)) { 1688 | // try to add data to send buffer 1689 | if (datasize > 0) { 1690 | if (conn->aio.dataUsed + datasize > conn->aio.dataSize) { 1691 | // we need more pepper! 1692 | int newsz = conn->aio.dataUsed + datasize; 1693 | char *nb = realloc(conn->aio.data, newsz); 1694 | // 1695 | if (nb == NULL) 1696 | return -1; // alas 1697 | conn->aio.data = nb; 1698 | conn->aio.dataSize = newsz; 1699 | } 1700 | // 1701 | memcpy(conn->aio.data + conn->aio.dataUsed, data, datasize); 1702 | conn->aio.dataUsed += datasize; 1703 | } 1704 | return 0; 1705 | } 1706 | // 1707 | return -1; 1708 | } 1709 | 1710 | //////////////////////////////////////////////////////////////////////////////// 1711 | int sam3aIsHaveActiveConnections(const Sam3ASession *ses) { 1712 | if (sam3aIsActiveSession(ses)) { 1713 | for (const Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { 1714 | if (sam3aIsActiveConnection(c)) 1715 | return 1; 1716 | } 1717 | } 1718 | return 0; 1719 | } 1720 | 1721 | //////////////////////////////////////////////////////////////////////////////// 1722 | int sam3aCancelConnection(Sam3AConnection *conn) { 1723 | if (conn != NULL) { 1724 | connDisconnect(conn); 1725 | return 0; 1726 | } 1727 | return -1; 1728 | } 1729 | 1730 | int sam3aCloseConnection(Sam3AConnection *conn) { 1731 | if (conn != NULL) { 1732 | sam3aCancelConnection(conn); 1733 | if (conn->cb.cbDestroy != NULL) 1734 | conn->cb.cbDestroy(conn); 1735 | for (Sam3AConnection *p = NULL, *c = conn->ses->connlist; c != NULL; 1736 | p = c, c = c->next) { 1737 | if (c == conn) { 1738 | // got it! 1739 | if (p == NULL) 1740 | c->ses->connlist = c->next; 1741 | else 1742 | p->next = c->next; 1743 | break; 1744 | } 1745 | } 1746 | if (conn->params != NULL) { 1747 | free(conn->params); 1748 | conn->params = NULL; 1749 | } 1750 | memset(conn, 0, sizeof(Sam3AConnection)); 1751 | free(conn); 1752 | } 1753 | return -1; 1754 | } 1755 | 1756 | //////////////////////////////////////////////////////////////////////////////// 1757 | int sam3aAddSessionToFDS(Sam3ASession *ses, int maxfd, fd_set *rds, 1758 | fd_set *wrs) { 1759 | if (ses != NULL) { 1760 | if (sam3aIsActiveSession(ses)) { 1761 | if (rds != NULL && ses->cbAIOProcessorR != NULL) { 1762 | if (maxfd < ses->fd) 1763 | maxfd = ses->fd; 1764 | FD_SET(ses->fd, rds); 1765 | } 1766 | // 1767 | if (wrs != NULL && ses->cbAIOProcessorW != NULL) { 1768 | if (maxfd < ses->fd) 1769 | maxfd = ses->fd; 1770 | FD_SET(ses->fd, wrs); 1771 | } 1772 | // 1773 | for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { 1774 | if (sam3aIsActiveConnection(c)) { 1775 | if (rds != NULL && c->cbAIOProcessorR != NULL) { 1776 | if (maxfd < c->fd) 1777 | maxfd = c->fd; 1778 | FD_SET(c->fd, rds); 1779 | } 1780 | // 1781 | if (wrs != NULL && c->cbAIOProcessorW != NULL) { 1782 | if (!c->callDisconnectCB || (c->aio.dataPos < c->aio.dataUsed)) 1783 | if (maxfd < c->fd) 1784 | maxfd = c->fd; 1785 | FD_SET(c->fd, wrs); 1786 | } 1787 | } 1788 | } 1789 | } 1790 | return maxfd; 1791 | } 1792 | // 1793 | return -1; 1794 | } 1795 | 1796 | void sam3aProcessSessionIO(Sam3ASession *ses, fd_set *rds, fd_set *wrs) { 1797 | if (sam3aIsActiveSession(ses)) { 1798 | if (ses->fd >= 0 && !ses->cancelled && ses->cbAIOProcessorR != NULL && 1799 | rds != NULL && FD_ISSET(ses->fd, rds)) 1800 | ses->cbAIOProcessorR(ses); 1801 | if (ses->fd >= 0 && !ses->cancelled && ses->cbAIOProcessorW != NULL && 1802 | wrs != NULL && FD_ISSET(ses->fd, wrs)) 1803 | ses->cbAIOProcessorW(ses); 1804 | // 1805 | for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { 1806 | if (c->fd >= 0 && !c->cancelled && c->cbAIOProcessorR != NULL && 1807 | rds != NULL && FD_ISSET(c->fd, rds)) 1808 | c->cbAIOProcessorR(c); 1809 | if (c->fd >= 0 && !c->cancelled && c->cbAIOProcessorW != NULL && 1810 | wrs != NULL && FD_ISSET(c->fd, wrs)) 1811 | c->cbAIOProcessorW(c); 1812 | } 1813 | } 1814 | } 1815 | -------------------------------------------------------------------------------- /src/libsam3a/libsam3a.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #ifndef LIBSAM3A_H 27 | #define LIBSAM3A_H 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #ifdef __MINGW32__ 37 | //#include 38 | #include 39 | #include 40 | #include 41 | //#define SOCK_CLOEXEC O_CLOEXEC 42 | //#define SOCK_NONBLOCK O_NONBLOCK 43 | #define SOCK_CLOEXEC 02000000 44 | #define SOCK_NONBLOCK FIONBIO 45 | #endif 46 | 47 | #ifdef __cplusplus 48 | extern "C" { 49 | #endif 50 | 51 | //////////////////////////////////////////////////////////////////////////////// 52 | /* 53 | * TODO: 54 | * [.] block sam3aClose*() in callbacks 55 | */ 56 | 57 | //////////////////////////////////////////////////////////////////////////////// 58 | extern int libsam3a_debug; 59 | 60 | //////////////////////////////////////////////////////////////////////////////// 61 | #define SAM3A_HOST_DEFAULT (NULL) 62 | #define SAM3A_PORT_DEFAULT (0) 63 | 64 | #define SAM3A_DESTINATION_TRANSIENT (NULL) 65 | 66 | #define SAM3A_PUBKEY_SIZE (516) 67 | #define SAM3A_PRIVKEY_SIZE (884) 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | extern uint64_t sam3atimeval2ms(const struct timeval *tv); 71 | extern void sam3ams2timeval(struct timeval *tv, uint64_t ms); 72 | 73 | //////////////////////////////////////////////////////////////////////////////// 74 | extern int sam3aIsValidPubKey(const char *key); 75 | extern int sam3aIsValidPrivKey(const char *key); 76 | 77 | //////////////////////////////////////////////////////////////////////////////// 78 | typedef struct Sam3ASession Sam3ASession; 79 | typedef struct Sam3AConnection Sam3AConnection; 80 | 81 | typedef enum { 82 | SAM3A_SESSION_RAW, 83 | SAM3A_SESSION_DGRAM, 84 | SAM3A_SESSION_STREAM 85 | } Sam3ASessionType; 86 | 87 | typedef struct { 88 | char *data; 89 | int dataSize; 90 | int dataUsed; 91 | int dataPos; 92 | void *udata; 93 | union { 94 | void (*cbReplyCheckSes)(Sam3ASession *ses); 95 | void (*cbReplyCheckConn)(Sam3AConnection *conn); 96 | }; 97 | } Sam3AIO; 98 | 99 | /** session callback functions */ 100 | typedef struct { 101 | void (*cbError)(Sam3ASession *ses); /** called on error */ 102 | void (*cbCreated)( 103 | Sam3ASession *ses); /** called when we created the session */ 104 | void (*cbDisconnected)(Sam3ASession *ses); /* call when closed; will called 105 | only after cbCreated() */ 106 | void (*cbDatagramRead)(Sam3ASession *ses, const void *buf, 107 | int bufsize); /* called when we got a datagram; bufsize 108 | >= 0; destkey set */ 109 | void (*cbDestroy)(Sam3ASession *ses); /* called when fd is already closed, but 110 | keys is not cleared */ 111 | } Sam3ASessionCallbacks; 112 | 113 | struct Sam3ASession { 114 | Sam3ASessionType type; /** session type */ 115 | int fd; /** socket file descriptor */ 116 | int cancelled; /** fd was shutdown()ed, but not closed yet */ 117 | char privkey[SAM3A_PRIVKEY_SIZE + 1]; /** private key (asciiz) */ 118 | char pubkey[SAM3A_PUBKEY_SIZE + 1]; /** public key (asciiz) */ 119 | char channel[66]; /** channel name (asciiz) */ 120 | char destkey[SAM3A_PUBKEY_SIZE + 1]; /** for DGRAM sessions (asciiz) */ 121 | char error[64]; /** error message (asciiz) */ 122 | uint32_t ip; /** ipv4 address of sam api interface */ 123 | int port; /** UDP port for DRAM/RAW (can be 0) */ 124 | Sam3AConnection *connlist; /** list of opened connections */ 125 | 126 | /** begin internal members */ 127 | // for async i/o 128 | Sam3AIO aio; 129 | void (*cbAIOProcessorR)(Sam3ASession *ses); // internal 130 | void (*cbAIOProcessorW)(Sam3ASession *ses); // internal 131 | int callDisconnectCB; 132 | char *params; // will be cleared only by sam3aCloseSession() 133 | int timeoutms; 134 | 135 | /** end internal members */ 136 | 137 | Sam3ASessionCallbacks cb; 138 | void *udata; 139 | }; 140 | 141 | /** connection callbacks for data sockets */ 142 | typedef struct { 143 | /** called on error */ 144 | void (*cbError)(Sam3AConnection *ct); 145 | /** called when closed or only after cbConnected()/cbAccepted(); note that 146 | * force disconnect is ok */ 147 | void (*cbDisconnected)(Sam3AConnection *ct); 148 | /** called when connected */ 149 | void (*cbConnected)(Sam3AConnection *ct); 150 | /** called instead of cbConnected() for sam3aStreamAccept*(), destkey filled 151 | * with remote destination */ 152 | void (*cbAccepted)(Sam3AConnection *ct); 153 | /** send callback, data sent, can add new data; will be called after 154 | * connect/accept */ 155 | void (*cbSent)(Sam3AConnection *ct); 156 | /** read callback, data read from socket (bufsize is always > 0) */ 157 | void (*cbRead)(Sam3AConnection *ct, const void *buf, int bufsize); 158 | /** fd already closed, but keys is not cleared */ 159 | void (*cbDestroy)(Sam3AConnection *ct); 160 | } Sam3AConnectionCallbacks; 161 | 162 | struct Sam3AConnection { 163 | /** parent session */ 164 | Sam3ASession *ses; 165 | Sam3AConnection *next; 166 | /** file descriptor */ 167 | int fd; 168 | int cancelled; // fd was shutdown()ed, but not closed yet 169 | char destkey[SAM3A_PUBKEY_SIZE + 1]; // (asciiz) 170 | char error[32]; // (asciiz) 171 | 172 | /** begin internal members */ 173 | // for async i/o 174 | Sam3AIO aio; 175 | void (*cbAIOProcessorR)(Sam3AConnection *ct); // internal 176 | void (*cbAIOProcessorW)(Sam3AConnection *ct); // internal 177 | int callDisconnectCB; 178 | char *params; // will be cleared only by sam3aCloseConnection() 179 | int timeoutms; 180 | /** end internal members */ 181 | 182 | /** callbacks */ 183 | Sam3AConnectionCallbacks cb; 184 | /** user data */ 185 | void *udata; 186 | }; 187 | 188 | //////////////////////////////////////////////////////////////////////////////// 189 | /* 190 | * check if session is active (i.e. have opened socket) 191 | * returns bool 192 | */ 193 | extern int sam3aIsActiveSession(const Sam3ASession *ses); 194 | 195 | /* 196 | * check if connection is active (i.e. have opened socket) 197 | * returns bool 198 | */ 199 | extern int sam3aIsActiveConnection(const Sam3AConnection *conn); 200 | 201 | //////////////////////////////////////////////////////////////////////////////// 202 | /* 203 | * note, that return error codes indicates invalid structure, pointer or fd 204 | * (i.e. immediate errors); all network errors indicated with cbError() callback 205 | */ 206 | 207 | /* 208 | * create SAM session 209 | * pass NULL as hostname for 'localhost' and 0 as port for 7656 210 | * pass NULL as privkey to create TRANSIENT session 211 | * 'params' can be NULL 212 | * see http://www.i2p2.i2p/i2cp.html#options for common options, 213 | * and http://www.i2p2.i2p/streaming.html#options for STREAM options 214 | * if result<0: error, 'ses' fields are undefined, no need to call 215 | * sam3aCloseSession() if result==0: ok, all 'ses' fields are filled 216 | * TODO: don't clear 'error' field on error (and set it to something meaningful) 217 | */ 218 | extern int sam3aCreateSessionEx(Sam3ASession *ses, 219 | const Sam3ASessionCallbacks *cb, 220 | const char *hostname, int port, 221 | const char *privkey, Sam3ASessionType type, 222 | const char *params, int timeoutms); 223 | 224 | static inline int sam3aCreateSession(Sam3ASession *ses, 225 | const Sam3ASessionCallbacks *cb, 226 | const char *hostname, int port, 227 | const char *privkey, 228 | Sam3ASessionType type) { 229 | return sam3aCreateSessionEx(ses, cb, hostname, port, privkey, type, NULL, -1); 230 | } 231 | 232 | /* returns <0 on error, 0 if no, >0 if yes */ 233 | extern int sam3aIsHaveActiveConnections(const Sam3ASession *ses); 234 | 235 | /* 236 | * close SAM session (and all it's connections) 237 | * returns <0 on error, 0 on ok 238 | * 'ses' must be properly initialized 239 | */ 240 | extern int sam3aCloseSession(Sam3ASession *ses); 241 | 242 | /* 243 | * cancel SAM session (and all it's connections), but don't free() or clear 244 | * anything except fds returns <0 on error, 0 on ok 'ses' must be properly 245 | * initialized 246 | */ 247 | extern int sam3aCancelSession(Sam3ASession *ses); 248 | 249 | /* 250 | * open stream connection to 'destkey' endpoint 251 | * 'destkey' is 516-byte public key (asciiz) 252 | * returns <0 on error 253 | * sets ses->error on memory or socket creation error 254 | */ 255 | extern Sam3AConnection *sam3aStreamConnectEx(Sam3ASession *ses, 256 | const Sam3AConnectionCallbacks *cb, 257 | const char *destkey, 258 | int timeoutms); 259 | 260 | static inline Sam3AConnection * 261 | sam3aStreamConnect(Sam3ASession *ses, const Sam3AConnectionCallbacks *cb, 262 | const char *destkey) { 263 | return sam3aStreamConnectEx(ses, cb, destkey, -1); 264 | } 265 | 266 | /* 267 | * accepts stream connection and sets 'destkey' 268 | * 'destkey' is 516-byte public key 269 | * returns <0 on error, fd on ok 270 | * you still have to call sam3aCloseSession() on failure 271 | * sets ses->error on error 272 | * note that there is no timeouts for now, but you can use sam3atcpSetTimeout*() 273 | */ 274 | extern Sam3AConnection *sam3aStreamAcceptEx(Sam3ASession *ses, 275 | const Sam3AConnectionCallbacks *cb, 276 | int timeoutms); 277 | 278 | static inline Sam3AConnection * 279 | sam3aStreamAccept(Sam3ASession *ses, const Sam3AConnectionCallbacks *cb) { 280 | return sam3aStreamAcceptEx(ses, cb, -1); 281 | } 282 | 283 | /* 284 | * close SAM connection, remove it from session and free memory 285 | * returns <0 on error, 0 on ok 286 | * 'conn' must be properly initialized 287 | * 'conn' is invalid after call 288 | */ 289 | extern int sam3aCloseConnection(Sam3AConnection *conn); 290 | 291 | /* 292 | * cancel SAM connection, but don't free() or clear anything except fd 293 | * returns <0 on error, 0 on ok 294 | * 'conn' must be properly initialized 295 | * 'conn' is invalid after call 296 | */ 297 | extern int sam3aCancelConnection(Sam3AConnection *conn); 298 | 299 | //////////////////////////////////////////////////////////////////////////////// 300 | /* 301 | * send data 302 | * this function can be used in cbSent() callback 303 | * 304 | * return: <0: error; 0: ok 305 | */ 306 | extern int sam3aSend(Sam3AConnection *conn, const void *data, int datasize); 307 | 308 | /* 309 | * sends datagram to 'destkey' endpoint 310 | * 'destkey' is 516-byte public key 311 | * returns <0 on error, 0 on ok 312 | * you still have to call sam3aCloseSession() on failure 313 | * sets ses->error on error 314 | * don't send datagrams bigger than 31KB! 315 | */ 316 | extern int sam3aDatagramSend(Sam3ASession *ses, const char *destkey, 317 | const void *buf, int bufsize); 318 | 319 | //////////////////////////////////////////////////////////////////////////////// 320 | /* 321 | * generate random channel name 322 | * dest should be at least (maxlen+1) bytes big 323 | */ 324 | extern int sam3aGenChannelName(char *dest, int minlen, int maxlen); 325 | 326 | //////////////////////////////////////////////////////////////////////////////// 327 | /* 328 | * generate new keypair 329 | * fills 'privkey' and 'pubkey' only 330 | * you should call sam3aCloseSession() on 'ses' 331 | * cbCreated callback will be called when keys generated 332 | * returns <0 on error, 0 on ok 333 | */ 334 | extern int sam3aGenerateKeysEx(Sam3ASession *ses, 335 | const Sam3ASessionCallbacks *cb, 336 | const char *hostname, int port, int timeoutms); 337 | 338 | static inline int sam3aGenerateKeys(Sam3ASession *ses, 339 | const Sam3ASessionCallbacks *cb, 340 | const char *hostname, int port) { 341 | return sam3aGenerateKeysEx(ses, cb, hostname, port, -1); 342 | } 343 | 344 | /* 345 | * do name lookup (something like gethostbyname()) 346 | * fills 'destkey' only 347 | * you should call sam3aCloseSession() on 'ses' 348 | * cbCreated callback will be called when keys generated, ses->destkey will be 349 | * set returns <0 on error, 0 on ok 350 | */ 351 | extern int sam3aNameLookupEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, 352 | const char *hostname, int port, const char *name, 353 | int timeoutms); 354 | 355 | static inline int sam3aNameLookup(Sam3ASession *ses, 356 | const Sam3ASessionCallbacks *cb, 357 | const char *hostname, int port, 358 | const char *name) { 359 | return sam3aNameLookupEx(ses, cb, hostname, port, name, -1); 360 | } 361 | 362 | //////////////////////////////////////////////////////////////////////////////// 363 | /* 364 | * append session fd to read and write sets if necessary 365 | * adds all alive session connections too 366 | * returns maxfd or -1 367 | * TODO: should keep fd count so it will not exceed FD_SETSIZE! 368 | */ 369 | extern int sam3aAddSessionToFDS(Sam3ASession *ses, int maxfd, fd_set *rds, 370 | fd_set *wrs); 371 | 372 | /* 373 | * process session i/o (and all session connections i/o) 374 | * should be called after successful select() 375 | */ 376 | extern void sam3aProcessSessionIO(Sam3ASession *ses, fd_set *rds, fd_set *wrs); 377 | 378 | //////////////////////////////////////////////////////////////////////////////// 379 | /* return malloc()ed buffer and len in 'plen' (if plen != NULL) */ 380 | extern char *sam3PrintfVA(int *plen, const char *fmt, va_list app); 381 | extern char *sam3Printf(int *plen, const char *fmt, ...) 382 | __attribute__((format(printf, 2, 3))); 383 | 384 | #ifdef __cplusplus 385 | } 386 | #endif 387 | #endif 388 | -------------------------------------------------------------------------------- /test/libsam3/test_b32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2023 I2P 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files 6 | * (the “Software”), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * http://git.idk.i2p/i2p-hackers/libsam3/ 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "../../src/ext/tinytest.h" 32 | #include "../../src/ext/tinytest_macros.h" 33 | #include "../../src/libsam3/libsam3.h" 34 | 35 | static int testb32(const char *src, const char *res) { 36 | size_t dlen = sam3Base32EncodedLength(strlen(src)), len; 37 | char dest[128]; 38 | // 39 | len = sam3Base32Encode(dest, sizeof(dest), src, strlen(src)); 40 | tt_int_op(len, ==, dlen); 41 | tt_int_op(len, ==, strlen(res)); 42 | tt_str_op(res, ==, dest); 43 | return 1; 44 | 45 | end: 46 | return 0; 47 | } 48 | 49 | void test_b32_encode(void *data) { 50 | (void)data; /* This testcase takes no data. */ 51 | 52 | tt_assert(testb32("", "")); 53 | tt_assert(testb32("f", "my======")); 54 | tt_assert(testb32("fo", "mzxq====")); 55 | tt_assert(testb32("foo", "mzxw6===")); 56 | tt_assert(testb32("foob", "mzxw6yq=")); 57 | tt_assert(testb32("fooba", "mzxw6ytb")); 58 | tt_assert(testb32("foobar", "mzxw6ytboi======")); 59 | 60 | end:; 61 | } 62 | 63 | struct testcase_t b32_tests[] = {{ 64 | "encode", 65 | test_b32_encode, 66 | }, 67 | END_OF_TESTCASES}; 68 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../src/ext/tinytest.h" 4 | #include "../src/ext/tinytest_macros.h" 5 | 6 | extern struct testcase_t b32_tests[]; 7 | 8 | struct testgroup_t test_groups[] = {{"b32/", b32_tests}, END_OF_GROUPS}; 9 | 10 | int main(int argc, const char **argv) { 11 | return tinytest_main(argc, argv, test_groups); 12 | } 13 | --------------------------------------------------------------------------------