├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── base32.h ├── catch.c ├── ipscope.h ├── speck_hash.h ├── speck_test.c ├── toss.c └── toss.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | @* 3 | *.o 4 | toss 5 | catch 6 | speck_test 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright 2017 ZeroTier, Inc. (Adam Ierymenko) 4 | https://www.zerotier.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including without limitation the 9 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR?=/usr/local/bin 2 | 3 | all: 4 | cc -O3 -std=c99 -Wall -Wno-unused-function -o toss toss.c 5 | cc -O3 -std=c99 -Wall -Wno-unused-function -o catch catch.c 6 | 7 | clean: 8 | rm -rf *.o toss catch speck_test *.dSYM 9 | 10 | distclean: clean 11 | 12 | realclean: clean 13 | 14 | install: 15 | mkdir -p $(DESTDIR) 16 | cp toss catch $(DESTDIR) 17 | chmod 0755 $(DESTDIR)/toss $(DESTDIR)/catch 18 | 19 | uninstall: 20 | rm -f $(DESTDIR)/toss $(DESTDIR)/catch 21 | 22 | speck_test: 23 | cc -O3 -std=c99 -o speck_test speck_test.c 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | toss: dead simple command line file transfer 2 | ====== 3 | 4 | Toss is a convenient ultra-minimal command line tool to send files over LAN, WiFi, and [virtual networks](https://www.zerotier.com/). 5 | 6 | ## Examples 7 | 8 | ### Toss a file between two terminal windows 9 | 10 | Sender: 11 | 12 | # toss nginx.conf 13 | nginx.conf/rpyaaaaaaaaaatevqcopx2r56xbo4bakxpjzgeh5qblmfyq4ertt3gmtnncrxyou 14 | 15 | Receiver: 16 | 17 | $ catch nginx.conf/rpyaaaaaaaaaatevqcopx2r56xbo4bakxpjzgeh5qblmfyq4ertt3gmtnncrxyou 18 | catch: catching nginx.conf (19605 bytes) 19 | catch: 10.187.211.147/35824 connected, reading... wrote 19605 bytes to: nginx.conf 20 | 21 | ### Toss a file to your team 22 | 23 | # toss debug-output.log 24 | debug-output.log/vd4qaaaaaaaaabqprfnhdzwnq3r2gbakxpjzgeh5qblmfyq4ertt3gmtnncrxyou 25 | 26 | Then paste the token into a chat system like IRC, Slack, etc. 27 | 28 | ### Stream a huge archive between systems 29 | 30 | Sender: 31 | 32 | # tar -czf - /usr | toss - 33 | ruz7777777777777tu3pjur2yqhtmbakxpjzgeh5qblmfyq4ertt3gmtnncrxyou 34 | 35 | Receiver: 36 | 37 | $ catch ruz7777777777777tu3pjur2yqhtmbakxpjzgeh5qblmfyq4ertt3gmtnncrxyou - | tar -tzf - 38 | 39 | ### Toss the output of a command to a team member 40 | 41 | $ ps aux | toss - 42 | zmr7777777777777grxowcyqr4w3gbakxpjzgeh5qblmfyq4ertt3gmtnncrxyou 43 | 44 | Then paste the token into a chat and your team member can do this: 45 | 46 | $ catch zmr7777777777777grxowcyqr4w3gbakxpjzgeh5qblmfyq4ertt3gmtnncrxyou - 47 | 48 | ... and see the output of your `ps aux` command. 49 | 50 | ## Description 51 | 52 | The `toss` program outputs a token generated from a random local TCP port, the size of the file you're sending, a hash of the file's contents (unless it's a pipe), and all the available IP addresses on all the interfaces in your system. It then listens for `catch` to connect and if presented with the correct claim token streams the file. If its input is a file it will continue to service `catch` requests one after the other (not concurrently) until it is terminated with CTRL+C or a kill signal. For pipes it terminates when the pipe closes. 53 | 54 | The `catch` program takes a `toss` token and then attempts to connect to all the IP addresses specified in it. If connection is successful it listens for a hello message (based on the hashed token) and if this is correct sends a claim message (a different version of the hashed token). If this exchange succeeds `toss` will send the file and `catch` will receive it. 55 | 56 | To toss a pipe, use `toss -`. To catch something and pipe it to standard output, use `catch -`. Both programs send status and error messages to standard error. 57 | 58 | The token can include a file name (the part before the slash) but this is optional. If this is not present `catch` will name the file based on its hash or the output file name provided on the command line. 59 | 60 | Transfers are done using TCP over ports between 30000 and 65535. You may have to configure your local firewall to allow this, or at least to allow it between certain IP addresses. 61 | 62 | Toss will work across the open Internet if no firewalls are in the way but it's mostly intended for LANs, WiFi networks, and [ZeroTier virtual networks](https://www.zerotier.com/) (plug, plug! we wrote this!). This little utility serves as an example of how easy things are if devices can communicate directly. 63 | 64 | ## Security 65 | 66 | Toss does no encryption and authentication is based on the token alone. Files are checked against a 64-bit hash, but pipes rely on TCP CRC checking alone. If you are transferring sensitive information over an un-trusted insecure network we highly recommend encrypting it with a real crypto tool like GPG or a similar. 67 | 68 | The `catch` command prioritizes private IP addresses and only tries globally scoped IPs after all attempts to use private ones have failed. 69 | 70 | ## Building 71 | 72 | On Linux, Mac, and BSD just type `make`. The source is self-contained and there are no dependencies. 73 | 74 | Some work has been done to prepare for a Windows port but this is incomplete. Pull requests are welcome. 75 | 76 | ## License 77 | 78 | MIT license. 79 | 80 | ## Changes 81 | 82 | * Version 1.1: add a DESTDIR to `make install` and make toss favor ZeroTier and tun/tap interfaces over physical ones. It just lists them first so catch will try them first. 83 | * Version 1.0: initial release! 84 | -------------------------------------------------------------------------------- /base32.h: -------------------------------------------------------------------------------- 1 | /* (c)2017 ZeroTier, Inc. (Adam Ierymenko) -- MIT LICENSE */ 2 | 3 | #ifndef TOSS_BASE32_H 4 | #define TOSS_BASE32_H 5 | 6 | #include 7 | 8 | static const char base32_chars[32] = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','2','3','4','5','6','7' }; 9 | static const uint8_t base32_bits[256] = { 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,26,27,28,29,30,31,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5, 12 | 6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0,0,1,2, 13 | 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 15 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 16 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 17 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 18 | }; 19 | 20 | static void base32_5_to_8(const uint8_t *in,char *out) 21 | { 22 | out[0] = base32_chars[(in[0]) >> 3]; 23 | out[1] = base32_chars[(in[0] & 0x07) << 2 | (in[1] & 0xc0) >> 6]; 24 | out[2] = base32_chars[(in[1] & 0x3e) >> 1]; 25 | out[3] = base32_chars[(in[1] & 0x01) << 4 | (in[2] & 0xf0) >> 4]; 26 | out[4] = base32_chars[(in[2] & 0x0f) << 1 | (in[3] & 0x80) >> 7]; 27 | out[5] = base32_chars[(in[3] & 0x7c) >> 2]; 28 | out[6] = base32_chars[(in[3] & 0x03) << 3 | (in[4] & 0xe0) >> 5]; 29 | out[7] = base32_chars[(in[4] & 0x1f)]; 30 | } 31 | 32 | static void base32_8_to_5(const char *in,uint8_t *out) 33 | { 34 | out[0] = ((base32_bits[(unsigned int)in[0]]) << 3) | (base32_bits[(unsigned int)in[1]] & 0x1C) >> 2; 35 | out[1] = ((base32_bits[(unsigned int)in[1]] & 0x03) << 6) | (base32_bits[(unsigned int)in[2]]) << 1 | (base32_bits[(unsigned int)in[3]] & 0x10) >> 4; 36 | out[2] = ((base32_bits[(unsigned int)in[3]] & 0x0F) << 4) | (base32_bits[(unsigned int)in[4]] & 0x1E) >> 1; 37 | out[3] = ((base32_bits[(unsigned int)in[4]] & 0x01) << 7) | (base32_bits[(unsigned int)in[5]]) << 2 | (base32_bits[(unsigned int)in[6]] & 0x18) >> 3; 38 | out[4] = ((base32_bits[(unsigned int)in[6]] & 0x07) << 5) | (base32_bits[(unsigned int)in[7]]); 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /catch.c: -------------------------------------------------------------------------------- 1 | /* (c)2017 ZeroTier, Inc. (Adam Ierymenko) -- MIT LICENSE */ 2 | 3 | #include "toss.h" 4 | 5 | #define TRY_SCOPE_COUNT 3 6 | static const enum toss_ip_scope TRY_SCOPE_ORDER[TRY_SCOPE_COUNT] = { IP_SCOPE_PRIVATE,IP_SCOPE_SHARED,IP_SCOPE_GLOBAL }; 7 | static const unsigned int TRY_SCOPE_TIMEOUT[TRY_SCOPE_COUNT] = { 2,2,8 }; 8 | 9 | static void catch_sigalrm(int sig) {} 10 | 11 | #if defined(_WIN32) || defined(_WIN64) 12 | int __cdecl _tmain(int argc, _TCHAR* argv[]) 13 | #else 14 | int main(int argc,char **argv) 15 | #endif 16 | { 17 | static uint8_t buf[1048576]; /* WARNING: can't use in multithreaded programs without making non-static */ 18 | char frombuf[128]; 19 | long n; 20 | struct speck_hash sh; 21 | 22 | #if defined(_WIN32) || defined(_WIN64) 23 | WSADATA wsaData; 24 | WSAStartup(MAKEWORD(2,2),&wsaData); 25 | #endif 26 | 27 | if ((argc < 2)||(argc > 3)) { 28 | printf("Usage: %s []\n",argv[0]); 29 | return 1; 30 | } 31 | 32 | char plainname[TOSS_MAX_TOKEN_BYTES]; 33 | uint8_t token[TOSS_MAX_TOKEN_BYTES + 8]; 34 | unsigned int tokenlen = 0; 35 | const char *hrtok = strchr(argv[1],'/'); 36 | if (hrtok) { 37 | unsigned int i = (unsigned int)(hrtok - argv[1]); 38 | memcpy(plainname,argv[1],i); 39 | plainname[i] = (char)0; 40 | ++hrtok; 41 | } else { 42 | plainname[0] = (char)0; 43 | hrtok = argv[1]; 44 | } 45 | while (strlen(hrtok) >= 8) { 46 | if (tokenlen >= TOSS_MAX_TOKEN_BYTES) { 47 | fprintf(stderr,"%s: FATAL: invalid token (too long)\n",argv[0]); 48 | return 1; 49 | } 50 | base32_8_to_5(hrtok,token + tokenlen); 51 | tokenlen += 5; 52 | hrtok += 8; 53 | } 54 | if ((tokenlen <= 18)||((tokenlen % 5) != 0)) { 55 | fprintf(stderr,"%s: FATAL: invalid or incomplete token (make sure you get both lines if it wraps in terminal)\n",argv[0]); 56 | return 1; 57 | } 58 | 59 | unsigned int port = (((unsigned int)token[0] & 0xff) << 8) | (token[1] & 0xff); 60 | if ((!port)||(port > 0xffff)) { 61 | fprintf(stderr,"%s: FATAL: invalid token (bad port %u)\n",argv[0],port); 62 | return 1; 63 | } 64 | uint64_t filelen = 0; 65 | filelen |= ((uint64_t)token[2] & 0xff) << 56; 66 | filelen |= ((uint64_t)token[3] & 0xff) << 48; 67 | filelen |= ((uint64_t)token[4] & 0xff) << 40; 68 | filelen |= ((uint64_t)token[5] & 0xff) << 32; 69 | filelen |= ((uint64_t)token[6] & 0xff) << 24; 70 | filelen |= ((uint64_t)token[7] & 0xff) << 16; 71 | filelen |= ((uint64_t)token[8] & 0xff) << 8; 72 | filelen |= (uint64_t)token[9] & 0xff; 73 | if (!filelen) { 74 | fprintf(stderr,"%s: FATAL: invalid token (file length is 0)\n",argv[0]); 75 | return 1; 76 | } 77 | 78 | if (!plainname[0]) { /* If no plainname, use file digest */ 79 | for(int i=0;i<8;++i) 80 | snprintf(plainname+(i*2),3,"%.2x",(unsigned int)token[i+10]); 81 | } 82 | const char *destpath = (argc >= 3) ? argv[2] : plainname; 83 | 84 | uint8_t claim[16]; 85 | speck_hash_reset(&sh); 86 | speck_hash_update(&sh,"toss1",5); 87 | speck_hash_update(&sh,token,tokenlen); 88 | speck_hash_update(&sh,"claim",5); 89 | speck_hash_finalize(&sh,claim); 90 | 91 | uint8_t hello[16]; 92 | speck_hash_reset(&sh); 93 | speck_hash_update(&sh,"toss1",5); 94 | speck_hash_update(&sh,token,tokenlen); 95 | speck_hash_update(&sh,"hello",5); 96 | speck_hash_finalize(&sh,hello); 97 | 98 | if (filelen == TOSS_PIPE_FILE_SIZE) { 99 | printf("%s: catching %s (size unknown)\n",argv[0],destpath); 100 | } else { 101 | printf("%s: catching %s (%llu bytes)\n",argv[0],destpath,(unsigned long long)filelen); 102 | } 103 | 104 | int ok = 1; 105 | for(int k=0;k= tokenlen) break; 109 | 110 | struct sockaddr_storage sa; 111 | const char *fromaddr = (char *)0; 112 | memset(&sa,0,sizeof(struct sockaddr_storage)); 113 | enum toss_ip_scope ipsc = IP_SCOPE_NONE; 114 | switch(iplen) { 115 | case 4: 116 | sa.ss_family = AF_INET; 117 | ((struct sockaddr_in *)&sa)->sin_port = htons((uint16_t)port); 118 | memcpy(&(((struct sockaddr_in *)&sa)->sin_addr.s_addr),token + i,4); 119 | fromaddr = inet_ntop(AF_INET,token + i,frombuf,sizeof(frombuf)); 120 | ipsc = classify_ip4((struct sockaddr_in *)&sa); 121 | break; 122 | case 16: 123 | sa.ss_family = AF_INET6; 124 | ((struct sockaddr_in6 *)&sa)->sin6_port = htons((uint16_t)port); 125 | memcpy(((struct sockaddr_in6 *)&sa)->sin6_addr.s6_addr,token + i,16); 126 | fromaddr = inet_ntop(AF_INET6,token + i,frombuf,sizeof(frombuf)); 127 | ipsc = classify_ip6((struct sockaddr_in6 *)&sa); 128 | break; 129 | } 130 | i += iplen; 131 | 132 | if ((fromaddr)&&(ipsc == TRY_SCOPE_ORDER[k])) { 133 | fprintf(stderr,"%s: %s/%u ",argv[0],fromaddr,port); fflush(stderr); 134 | 135 | int csock = socket(sa.ss_family,SOCK_STREAM,0); 136 | if (csock < 0) { 137 | fprintf(stderr,"%s: FATAL: socket() failed\n",argv[0]); 138 | return 1; 139 | } 140 | 141 | #ifdef TOSS_CATCH_MAX_HOPS 142 | if (sa.ss_family == AF_INET) { 143 | int opt = TOSS_CATCH_MAX_HOPS; 144 | setsockopt(csock,IPPROTO_IP,IP_TTL,(void *)&opt,sizeof(opt)); 145 | } else if (sa.ss_family == AF_INET6) { 146 | int opt = TOSS_CATCH_MAX_HOPS; 147 | setsockopt(csock,IPPROTO_IPV6,IPV6_UNICAST_HOPS,(void *)&opt,sizeof(opt)); 148 | } 149 | #endif 150 | 151 | #if defined(_WIN32) || defined(_WIN64) 152 | #error No timeout implementation for Windows yet. 153 | #else 154 | signal(SIGALRM,catch_sigalrm); 155 | alarm(TRY_SCOPE_TIMEOUT[k]); 156 | #endif 157 | 158 | if (connect(csock,(struct sockaddr *)&sa,(sa.ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) { 159 | alarm(0); 160 | close(csock); 161 | fprintf(stderr,"connect failed.\n"); 162 | continue; 163 | } 164 | alarm(0); 165 | 166 | long helloptr = 0; 167 | while ((helloptr < 16)&&((n = recv(csock,(void *)(buf + helloptr),16 - helloptr,0)) > 0)) 168 | helloptr += n; 169 | if (helloptr != 16) { 170 | close(csock); 171 | fprintf(stderr,"bad greeting (incomplete).\n"); 172 | continue; 173 | } 174 | if (memcmp(buf,hello,16)) { 175 | close(csock); 176 | fprintf(stderr,"bad greeting (invalid).\n"); 177 | continue; 178 | } 179 | 180 | send(csock,claim,16,0); 181 | 182 | int filefd; 183 | if (!strcmp(destpath,"-")) { 184 | filefd = STDOUT_FILENO; 185 | } else { 186 | filefd = open(destpath,O_WRONLY|O_CREAT|O_TRUNC,0644); 187 | if (filefd < 0) { 188 | close(csock); 189 | fprintf(stderr,"cannot open file for writing.\n%s: FATAL: cannot open destination for writing: %s",argv[0],destpath); 190 | return 1; 191 | } 192 | } 193 | 194 | uint64_t filegot = 0; 195 | speck_hash_reset(&sh); 196 | fprintf(stderr,"connected, reading... "); fflush(stderr); 197 | while (((n = recv(csock,buf,sizeof(buf),0)) > 0)&&(filegot < filelen)) { 198 | speck_hash_update(&sh,buf,(unsigned long)n); 199 | if ((long)write(filefd,buf,n) != n) { 200 | close(csock); 201 | close(filefd); 202 | fprintf(stderr,"write error.\n%s: FATAL: write error: %s",argv[0],destpath); 203 | return 1; 204 | } 205 | filegot += (uint64_t)n; 206 | } 207 | close(csock); 208 | close(filefd); 209 | 210 | speck_hash_finalize(&sh,buf); 211 | if ((filelen != TOSS_PIPE_FILE_SIZE)&&((memcmp(token + 10,buf,8))||(filelen != filegot))) { 212 | fprintf(stderr,"got %llu bytes, VERIFICATION FAILED! file may be corrupt!\n",(unsigned long long)filegot); 213 | } else { 214 | ok = 0; 215 | fprintf(stderr,"wrote %llu bytes to: %s\n",(unsigned long long)filegot,(filefd == STDOUT_FILENO) ? "(stdout)" : destpath); 216 | } 217 | 218 | k = TRY_SCOPE_COUNT + 1; /* break outer loop */ 219 | break; 220 | } 221 | } 222 | } 223 | 224 | if (ok) 225 | printf("%s: no addresses worked! bad token or no network path?\n",argv[0]); 226 | 227 | return ok; 228 | } 229 | -------------------------------------------------------------------------------- /ipscope.h: -------------------------------------------------------------------------------- 1 | /* (c)2017 ZeroTier, Inc. (Adam Ierymenko) -- MIT LICENSE */ 2 | 3 | #ifndef TOSS_IPSCOPE_H 4 | #define TOSS_IPSCOPE_H 5 | 6 | #include 7 | 8 | enum toss_ip_scope 9 | { 10 | IP_SCOPE_NONE, /* NULL or not an IP address */ 11 | IP_SCOPE_MULTICAST, /* 224.0.0.0 and other V4/V6 multicast IPs */ 12 | IP_SCOPE_LOOPBACK, /* 127.0.0.1, ::1, etc. */ 13 | IP_SCOPE_GLOBAL, /* globally routable IP address (all others) */ 14 | IP_SCOPE_LINK_LOCAL, /* 169.254.x.x, IPv6 LL */ 15 | IP_SCOPE_SHARED, /* 100.64.0.0/10, shared space for e.g. carrier-grade NAT */ 16 | IP_SCOPE_PRIVATE /* 10.x.x.x, 192.168.x.x, etc. */ 17 | }; 18 | 19 | static enum toss_ip_scope classify_ip4(const struct sockaddr_in *in) 20 | { 21 | const uint32_t ip = (uint32_t)ntohl(in->sin_addr.s_addr); 22 | switch(ip >> 24) { 23 | case 0x00: return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) 24 | case 0x0a: return IP_SCOPE_PRIVATE; // 10.0.0.0/8 25 | case 0x64: 26 | if ((ip & 0xffc00000) == 0x64400000) return IP_SCOPE_SHARED; // 100.64.0.0/10 27 | break; 28 | case 0x7f: return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 29 | case 0xa9: 30 | if ((ip & 0xffff0000) == 0xa9fe0000) return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 31 | break; 32 | case 0xac: 33 | if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE; // 172.16.0.0/12 34 | break; 35 | case 0xc0: 36 | if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 37 | break; 38 | case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) 39 | } 40 | switch(ip >> 28) { 41 | case 0xe: return IP_SCOPE_MULTICAST; // 224.0.0.0/4 42 | } 43 | return IP_SCOPE_GLOBAL; 44 | } 45 | 46 | static enum toss_ip_scope classify_ip6(const struct sockaddr_in6 *in) 47 | { 48 | const uint8_t *ip = (const uint8_t *)in->sin6_addr.s6_addr; 49 | if ((ip[0] & 0xf0) == 0xf0) { 50 | if (ip[0] == 0xff) return IP_SCOPE_MULTICAST; // ff00::/8 51 | if ((ip[0] == 0xfe)&&((ip[1] & 0xc0) == 0x80)) { 52 | unsigned int k = 2; 53 | while ((!ip[k])&&(k < 15)) ++k; 54 | if ((k == 15)&&(ip[15] == 0x01)) 55 | return IP_SCOPE_LOOPBACK; // fe80::1/128 56 | else return IP_SCOPE_LINK_LOCAL; // fe80::/10 57 | } 58 | if ((ip[0] & 0xfe) == 0xfc) return IP_SCOPE_PRIVATE; // fc00::/7 59 | } 60 | unsigned int k = 0; 61 | while ((!ip[k])&&(k < 15)) ++k; 62 | if (k == 15) { // all 0's except last byte 63 | if (ip[15] == 0x01) return IP_SCOPE_LOOPBACK; // ::1/128 64 | if (ip[15] == 0x00) return IP_SCOPE_NONE; // ::/128 65 | } 66 | return IP_SCOPE_GLOBAL; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /speck_hash.h: -------------------------------------------------------------------------------- 1 | /* (c)2017 ZeroTier, Inc. (Adam Ierymenko) -- MIT LICENSE */ 2 | 3 | #ifndef TOSS_SPECK_HASH_H 4 | #define TOSS_SPECK_HASH_H 5 | 6 | /* This is used for payload verification and generating a claim code. 7 | * At only 128 bits and built from a "weird" block cipher, it's should 8 | * not be considered a strong hash. Toss is not a crypto tool, so 9 | * use something like GPG to encrypt your content prior to transferring 10 | * it if you need better security. */ 11 | 12 | #include 13 | 14 | #define SPECK_ROR(x, r) ((x >> r) | (x << (64 - r))) 15 | #define SPECK_ROL(x, r) ((x << r) | (x >> (64 - r))) 16 | #define SPECK_R(x, y, k) (x = SPECK_ROR(x, 8), x += y, x ^= k, y = SPECK_ROL(y, 3), y ^= x) 17 | #define SPECK_ROUNDS 32 18 | 19 | /* Speck: a super-tiny ARX block cipher (128-bit variant) */ 20 | /* https://en.wikipedia.org/wiki/Speck_%28cipher%29 */ 21 | static void speck_encrypt(uint64_t pt[2],uint64_t ct[2],uint64_t K[2]) 22 | { 23 | uint64_t y = pt[0], x = pt[1], b = K[0], a = K[1]; 24 | SPECK_R(x, y, b); 25 | for (int i = 0; i < SPECK_ROUNDS - 1; i++) { 26 | SPECK_R(a, b, i); 27 | SPECK_R(x, y, b); 28 | } 29 | ct[0] = y; 30 | ct[1] = x; 31 | } 32 | 33 | /* Simple 128-bit Merkle–Damgård hash function built from Speck */ 34 | /* https://en.wikipedia.org/wiki/Merkle–Damgård_construction */ 35 | 36 | struct speck_hash 37 | { 38 | uint64_t digest[2]; 39 | uint64_t nextblk[2]; 40 | unsigned long nextblkptr; 41 | unsigned long totallen; 42 | }; 43 | 44 | static void speck_hash_reset(struct speck_hash *h) 45 | { 46 | /* Silly arbitrary IV */ 47 | h->digest[0] = 0xfeeddeadbabef00dULL; 48 | h->digest[1] = 0xfeeddeadd00df00dULL; 49 | h->nextblk[0] = 0; 50 | h->nextblk[1] = 0; 51 | h->nextblkptr = 0; 52 | h->totallen = 0; 53 | } 54 | 55 | static void speck_hash_update(struct speck_hash *h,const void *d,unsigned long l) 56 | { 57 | uint64_t tmp[2]; 58 | for(unsigned long i=0;inextblk[h->nextblkptr >> 3] |= ( ((uint64_t)((const uint8_t *)d)[i] & (uint64_t)0xff) << (8 * (h->nextblkptr & 7)) ); 60 | if (++h->nextblkptr == 16) { 61 | speck_encrypt(h->digest,tmp,h->nextblk); 62 | h->digest[0] ^= tmp[0]; 63 | h->digest[1] ^= tmp[1]; 64 | h->nextblkptr = 0; 65 | h->nextblk[0] = 0; 66 | h->nextblk[1] = 0; 67 | } 68 | } 69 | h->totallen += l; 70 | } 71 | 72 | static void speck_hash_finalize(struct speck_hash *h,uint8_t digest[16]) 73 | { 74 | uint64_t pad[2],tmp[2]; 75 | 76 | /* Hash any remaining input */ 77 | if (h->nextblkptr) 78 | speck_encrypt(h->digest,tmp,h->nextblk); 79 | 80 | /* Merkle–Damgård length padding */ 81 | pad[0] = (uint64_t)h->totallen; 82 | pad[1] = ~((uint64_t)h->totallen); 83 | speck_encrypt(h->digest,tmp,pad); 84 | h->digest[0] ^= tmp[0]; 85 | h->digest[1] ^= tmp[1]; 86 | 87 | /* Output digest in big-endian byte order */ 88 | digest[0] = (uint8_t)((h->digest[0] >> 56) & 0xff); 89 | digest[1] = (uint8_t)((h->digest[0] >> 48) & 0xff); 90 | digest[2] = (uint8_t)((h->digest[0] >> 40) & 0xff); 91 | digest[3] = (uint8_t)((h->digest[0] >> 32) & 0xff); 92 | digest[4] = (uint8_t)((h->digest[0] >> 24) & 0xff); 93 | digest[5] = (uint8_t)((h->digest[0] >> 16) & 0xff); 94 | digest[6] = (uint8_t)((h->digest[0] >> 8) & 0xff); 95 | digest[7] = (uint8_t)(h->digest[0] & 0xff); 96 | digest[8] = (uint8_t)((h->digest[1] >> 56) & 0xff); 97 | digest[9] = (uint8_t)((h->digest[1] >> 48) & 0xff); 98 | digest[10] = (uint8_t)((h->digest[1] >> 40) & 0xff); 99 | digest[11] = (uint8_t)((h->digest[1] >> 32) & 0xff); 100 | digest[12] = (uint8_t)((h->digest[1] >> 24) & 0xff); 101 | digest[13] = (uint8_t)((h->digest[1] >> 16) & 0xff); 102 | digest[14] = (uint8_t)((h->digest[1] >> 8) & 0xff); 103 | digest[15] = (uint8_t)(h->digest[1] & 0xff); 104 | } 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /speck_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "speck_hash.h" 6 | 7 | int main(int argc,char **argv) 8 | { 9 | static uint8_t tmp[1000001]; 10 | uint8_t digest[16]; 11 | struct speck_hash sh; 12 | 13 | for(long i=0;i\n",argv[0]); 25 | return 1; 26 | } 27 | 28 | #if defined(_WIN32) || defined(_WIN64) 29 | const char *plainname = strrchr(argv[1],'\\'); 30 | if (plainname) 31 | ++plainname; 32 | else plainname = argv[1]; 33 | if (strchr(plainname,'/')) { 34 | fprintf(stderr,"%s: FATAL: / is not allowed in a file name.\n",argv[0]); 35 | return 1; 36 | } 37 | #else 38 | const char *plainname = strrchr(argv[1],'/'); 39 | if (plainname) 40 | ++plainname; 41 | else plainname = argv[1]; 42 | if (strchr(plainname,'\\')) { 43 | fprintf(stderr,"%s: FATAL: \\ is not allowed in a file name.\n",argv[0]); 44 | return 1; 45 | } 46 | #endif 47 | 48 | uint8_t ip4s[TOSS_MAX_TOKEN_BYTES],ip6s[TOSS_MAX_TOKEN_BYTES]; 49 | unsigned int ip4ptr = 0,ip6ptr = 0; 50 | 51 | #if defined(_WIN32) || defined(_WIN64) 52 | #error Windows interface address enumeration not implemented yet. 53 | #else 54 | /* We do this in four passes. The first grabs interfaces that start with 55 | * 'z' (zt#), the second 't' (tun#/tap#), the third 'i' (ipsec#), and the 56 | * final pass grabs anything else (eth#, etc.). This makes us prioritize 57 | * encapsulated encrypted interaces like ZeroTier and OpenVPN over others. 58 | * Catch has its own priority, trying private IPs first and then globally 59 | * scoped ones. These two priorities work together to prefer secure and 60 | * possibly virtual routes. */ 61 | struct ifaddrs *ifalist = (struct ifaddrs *)0; 62 | if (getifaddrs(&ifalist)) { 63 | fprintf(stderr,"%s: FATAL: getifaddrs() failed (call failed).\n",argv[0]); 64 | return 1; 65 | } 66 | if (!ifalist) { 67 | fprintf(stderr,"%s: FATAL: getifaddrs() failed (null result).\n",argv[0]); 68 | return 1; 69 | } 70 | for(int pass=0;pass<4;++pass) { 71 | struct ifaddrs *ifa = ifalist; 72 | while (ifa) { 73 | if ( (ifa->ifa_addr) && ( (pass == 3) || ( (ifa->ifa_name) && (ifa->ifa_name[0] == "zti"[pass]) ) ) ) { 74 | enum toss_ip_scope ipscope = IP_SCOPE_NONE; 75 | switch(ifa->ifa_addr->sa_family) { 76 | case AF_INET: 77 | if ((ip4ptr + 4) <= TOSS_MAX_TOKEN_BYTES) { 78 | ipscope = classify_ip4((struct sockaddr_in *)ifa->ifa_addr); 79 | if ((ipscope == IP_SCOPE_PRIVATE)||(ipscope == IP_SCOPE_GLOBAL)||(ipscope == IP_SCOPE_SHARED)) { 80 | memcpy(ip4s + ip4ptr,&(((const struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr),4); 81 | ip4ptr += 4; 82 | } 83 | } 84 | break; 85 | case AF_INET6: 86 | if ((ip6ptr + 16) <= TOSS_MAX_TOKEN_BYTES) { 87 | ipscope = classify_ip6((struct sockaddr_in6 *)ifa->ifa_addr); 88 | if ((ipscope == IP_SCOPE_PRIVATE)||(ipscope == IP_SCOPE_GLOBAL)||(ipscope == IP_SCOPE_SHARED)) { 89 | memcpy(ip6s + ip6ptr,((const struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr,16); 90 | ip6ptr += 16; 91 | } 92 | } 93 | break; 94 | } 95 | } 96 | ifa = ifa->ifa_next; 97 | } 98 | } 99 | freeifaddrs(ifalist); 100 | #endif 101 | 102 | int filefd; 103 | uint8_t filedigest[16]; 104 | uint64_t filelen = 0; 105 | if (!strcmp(plainname,"-")) { 106 | filefd = STDIN_FILENO; 107 | plainname = (char *)0; /* will be filled in later */ 108 | for(int i=0;i<16;++i) 109 | filedigest[i] = (uint8_t)rand(); /* digest is unused with pipes, so randomize it to randomize the token */ 110 | filelen = TOSS_PIPE_FILE_SIZE; 111 | } else { 112 | filefd = open(argv[1],O_RDONLY); 113 | if (filefd < 0) { 114 | fprintf(stderr,"%s: FATAL: unable to open for reading: %s\n",argv[0],argv[1]); 115 | return 1; 116 | } 117 | speck_hash_reset(&sh); 118 | while ((n = (long)read(filefd,buf,sizeof(buf))) > 0) { 119 | filelen += (uint64_t)n; 120 | speck_hash_update(&sh,buf,(unsigned long)n); 121 | } 122 | speck_hash_finalize(&sh,filedigest); 123 | if (!filelen) { 124 | close(filefd); 125 | fprintf(stderr,"%s: FATAL: zero byte file: %s\n",argv[0],argv[1]); 126 | return 1; 127 | } 128 | } 129 | 130 | int lsock = -1; 131 | unsigned int port = 0; 132 | for(int k=0;k<16384;++k) { 133 | lsock = (int)socket(AF_INET6,SOCK_STREAM,0); 134 | if (lsock < 0) { 135 | close(filefd); 136 | fprintf(stderr,"%s: FATAL: socket(AF_INET6,SOCK_STREAM,0) failed.\n",argv[0]); 137 | return 1; 138 | } 139 | int tmpi = 0; 140 | setsockopt(lsock,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&tmpi,sizeof(tmpi)); 141 | tmpi = 1; 142 | setsockopt(lsock,SOL_SOCKET,SO_REUSEADDR,(void *)&tmpi,sizeof(tmpi)); 143 | 144 | port = 30000 + ((unsigned int)rand() % 35535); 145 | struct sockaddr_in6 in6any; 146 | memset(&in6any,0,sizeof(struct sockaddr_in6)); 147 | in6any.sin6_family = AF_INET6; 148 | in6any.sin6_port = htons((uint16_t)port); 149 | in6any.sin6_addr = in6addr_any; 150 | 151 | if (bind(lsock,(struct sockaddr *)&in6any,sizeof(struct sockaddr_in6))) { 152 | close(lsock); 153 | lsock = -1; 154 | } else { 155 | break; 156 | } 157 | } 158 | if (lsock < 0) { 159 | close(filefd); 160 | fprintf(stderr,"%s: FATAL: unable to bind any port (tried 16384 times)\n",argv[0]); 161 | return 1; 162 | } 163 | 164 | uint8_t token[TOSS_MAX_TOKEN_BYTES + 8]; 165 | token[0] = (uint8_t)((port >> 8) & 0xff); 166 | token[1] = (uint8_t)(port & 0xff); 167 | token[2] = (uint8_t)((filelen >> 56) & 0xff); 168 | token[3] = (uint8_t)((filelen >> 48) & 0xff); 169 | token[4] = (uint8_t)((filelen >> 40) & 0xff); 170 | token[5] = (uint8_t)((filelen >> 32) & 0xff); 171 | token[6] = (uint8_t)((filelen >> 24) & 0xff); 172 | token[7] = (uint8_t)((filelen >> 16) & 0xff); 173 | token[8] = (uint8_t)((filelen >> 8) & 0xff); 174 | token[9] = (uint8_t)(filelen & 0xff); 175 | unsigned int tokenlen = 10; 176 | for(int i=0;i<8;++i) /* use first 8 bytes of file digest */ 177 | token[tokenlen++] = filedigest[i]; 178 | unsigned int ip4ptr2 = 0,ip6ptr2 = 0; 179 | n = 0; 180 | while ((ip4ptr2 < ip4ptr)&&(ip6ptr2 < ip6ptr)&&((tokenlen + 17 + 5) <= TOSS_MAX_TOKEN_BYTES)) { 181 | if (n) { 182 | token[tokenlen++] = 16; 183 | for(int i=0;i<16;++i) 184 | token[tokenlen++] = ip6s[ip6ptr2++]; 185 | } else { 186 | token[tokenlen++] = 4; 187 | for(int i=0;i<4;++i) 188 | token[tokenlen++] = ip4s[ip4ptr2++]; 189 | } 190 | n ^= 1; 191 | } 192 | while ((tokenlen % 5) != 0) 193 | ++tokenlen; 194 | 195 | char hrtok[TOSS_MAX_TOKEN_BYTES * 2]; 196 | n = 0; 197 | for(int i=0;isin_addr.s_addr),frombuf,sizeof(frombuf)); 237 | break; 238 | case AF_INET6: 239 | fromasc = inet_ntop(AF_INET6,((struct sockaddr_in6 *)&fromaddr)->sin6_addr.s6_addr,frombuf,sizeof(frombuf)); 240 | break; 241 | } 242 | if (!fromasc) 243 | fromasc = "(unknown)"; 244 | 245 | fprintf(stderr,"%s: %s ",argv[0],fromasc); fflush(stderr); 246 | 247 | send(csock,hello,16,0); 248 | 249 | long claimptr = 0; 250 | while ((claimptr < 16)&&((n = recv(csock,(void *)(buf + claimptr),16 - claimptr,0)) > 0)) 251 | claimptr += n; 252 | if ((claimptr != 16)||(memcmp(buf,claim,16))) { 253 | close(csock); 254 | fprintf(stderr,"invalid claim code.\n"); 255 | continue; 256 | } 257 | 258 | fprintf(stderr,"claim OK... "); fflush(stderr); 259 | if (filefd == STDIN_FILENO) { 260 | n = 0; 261 | uint64_t wrote = 0; 262 | while ((n = read(filefd,buf,sizeof(buf))) > 0) { 263 | if ((long)send(csock,buf,n,0) != n) { 264 | fprintf(stderr,"send incomplete, wrote %llu bytes.\n",(unsigned long long)wrote); 265 | break; 266 | } 267 | wrote += n; 268 | } 269 | fprintf(stderr,"tossed %llu bytes.\n",(unsigned long long)wrote); 270 | shutdown(csock,SHUT_WR); 271 | close(csock); 272 | break; 273 | } else { 274 | lseek(filefd,0,SEEK_SET); 275 | off_t flen = (off_t)filelen; 276 | #if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) 277 | if (sendfile(csock,filefd,(off_t *)0,(size_t)filelen) < 0) { 278 | #else 279 | if (sendfile(filefd,csock,0,&flen,(struct sf_hdtr *)0,0)) { 280 | #endif 281 | fprintf(stderr,"sendfile() failed.\n"); 282 | } else if (flen != (off_t)filelen) { 283 | fprintf(stderr,"sendfile() incomplete, wrote %llu bytes.\n",(unsigned long long)flen); 284 | } else { 285 | fprintf(stderr,"tossed %llu bytes.\n",(unsigned long long)filelen); 286 | shutdown(csock,SHUT_WR); 287 | } 288 | close(csock); 289 | } 290 | } 291 | 292 | close(filefd); 293 | close(lsock); 294 | 295 | return 0; 296 | } 297 | -------------------------------------------------------------------------------- /toss.h: -------------------------------------------------------------------------------- 1 | /* (c)2017 ZeroTier, Inc. (Adam Ierymenko) -- MIT LICENSE */ 2 | 3 | #ifndef TOSS_H__ 4 | #define TOSS_H__ 5 | 6 | #if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) 7 | #undef _FILE_OFFSET_BITS 8 | #define _FILE_OFFSET_BITS 64 9 | #endif 10 | 11 | #define TOSS_VERSION "1.1" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(_WIN32) || defined(_WIN64) 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #else 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) 37 | #include 38 | #endif 39 | #endif 40 | 41 | #include "ipscope.h" 42 | #include "speck_hash.h" 43 | #include "base32.h" 44 | 45 | /* Do not change, must be a multiple of 5 */ 46 | #define TOSS_MAX_TOKEN_BYTES 500 47 | 48 | /* Max IP hops (IP TTL) for catch */ 49 | /* #define TOSS_CATCH_MAX_HOPS 2 */ 50 | 51 | /* Size indicating the "file" is a pipe */ 52 | #define TOSS_PIPE_FILE_SIZE 0xffffffffffffffffULL 53 | 54 | #endif 55 | --------------------------------------------------------------------------------