├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bin2sh.c └── uget.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | uget 3 | bin2sh 4 | uget.sh 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OpenIPC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-Os 2 | CROSS_COMPILE?=arm-hisiv510-linux- 3 | CC=$(CROSS_COMPILE)gcc 4 | STRIP=$(CROSS_COMPILE)strip 5 | 6 | BINARIES=uget bin2sh 7 | 8 | all: $(BINARIES) 9 | 10 | uget: uget.o 11 | $(CC) $(CFLAGS) -o $@ $^ 12 | $(STRIP) -R .comment -R .note -R .note.ABI-tag $@ 13 | upx $@ 14 | 15 | bin2sh: bin2sh.c 16 | cc -o $@ $^ 17 | 18 | clean: 19 | -rm -f uget $(BINARIES) *.o 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # μget 2 | 3 | Simple utility to help CI guys do their job in constrained environments to 4 | download and run binaries in `/tmp`. Size of μget is around 4Kb. 5 | 6 | ## Features 7 | 8 | * Could be used as `curl` or `wget` replacement. In my case it's convenient to 9 | PUT binary to S3 bucket and then run it on device 10 | 11 | * Supports only `HTTP` (we need deal with size), no redirects yet 12 | 13 | * No fancy error messages inside binary, returns only exit codes from defined 14 | preset (see `Error codes` section). 15 | 16 | ## Usage 17 | 18 | * As `curl` replacement: `uget ifconfig.me` 19 | 20 | * Ad-hoc utility to download and run binary: 21 | 22 | ```sh 23 | $ ./uget run openipc.s3-eu-west-1.amazonaws.com/ipc_chip_info 24 | ``` 25 | 26 | ## Transferring to device using telnet 27 | 28 | ```console 29 | # on your Linux workstation 30 | $ ./bin2sh uget > uget.sh 31 | # login via telnet to embedded device, copy-paste text from uget.sh 32 | # target binary will reside in /tmp 33 | $ ./uget example.com 34 | 35 | # if target system doesn't support printf use 36 | $ ./bin2sh -echo uget > uget.sh 37 | ``` 38 | 39 | ## Demo 40 | 41 | [![asciicast](https://asciinema.org/a/QeQTnRudeNPOMW6s1KCZXosf5.svg)](https://asciinema.org/a/QeQTnRudeNPOMW6s1KCZXosf5) 42 | 43 | ## Error codes 44 | 45 | |Error code|Description| 46 | |---|---| 47 | | 0 | everything is ok | 48 | | 1 | general error code | 49 | | 2 | socket creation error | 50 | | 3 | DNS resolution error | 51 | | 4 | connection error | 52 | | 5 | send error | 53 | | 6 | incorrect command line options | 54 | 55 | * HTTP response codes other than 2XX are transformed from `XYZ` number to `XZ` exit 56 | code (so `44` means `404`, or `52` means `502`) 57 | -------------------------------------------------------------------------------- /bin2sh.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const char *trtable[256] = { 6 | "\\0", // 0 7 | NULL, // 1 8 | NULL, // 2 9 | NULL, // 3 10 | NULL, // 4 11 | NULL, // 5 12 | NULL, // 6 13 | "\\a", // 7 14 | "\\b", // 8 15 | "\\t", // 9 16 | "\\n", // 10 17 | "\\v", // 11 18 | "\\f", // 12 19 | "\\r", // 13 20 | }; 21 | 22 | int printable(char ch) { 23 | return ch != '`' && ch != '"' && ch != '\\' && (ch >= 'a' && ch <= 'z'); 24 | } 25 | 26 | #define SHELL_INPUT_MAX 700 27 | 28 | int main(int argc, char **argv) { 29 | int printf_mode = 1; 30 | const char *input = NULL; 31 | 32 | if (argc == 3) { 33 | // treat first argument as optional key 34 | if (!strcmp(argv[1], "-echo")) { 35 | printf_mode = 0; 36 | input = argv[2]; 37 | } 38 | } else if (argc == 2) { 39 | input = argv[1]; 40 | } 41 | if (!input) { 42 | printf("Usage: %s [-echo] binary > textfile\n", argv[0]); 43 | exit(EXIT_FAILURE); 44 | } 45 | 46 | FILE *f = fopen(input, "rb"); 47 | if (!f) { 48 | printf("Error while open %s\n", input); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | unsigned char byte; 53 | 54 | int line = 0, chout = 0; 55 | const char *newf = ">"; 56 | const char *exst = ">>"; 57 | const char *echo_ = "echo -ne \""; 58 | const char *printf_ = "printf \""; 59 | 60 | fprintf(stdout, "cd /tmp;F=%s;true>$F;chmod +x $F\n", input); 61 | 62 | while (fread(&byte, 1, sizeof(byte), f)) { 63 | if (chout == 0) { 64 | chout += fprintf(stdout, "%s", printf_mode ? printf_ : echo_); 65 | line++; 66 | } 67 | if (byte == '"') 68 | chout += fprintf(stdout, "\""); 69 | else if (trtable[byte]) 70 | chout += fprintf(stdout, "%s", trtable[byte]); 71 | else 72 | chout += fprintf(stdout, "\\x%X", byte); 73 | if (chout > SHELL_INPUT_MAX) { 74 | fprintf(stdout, "\"%s%s\n", line == 1 ? newf : exst, "$F"); 75 | chout = 0; 76 | } 77 | } 78 | if (chout) { 79 | fprintf(stdout, "\"%s%s\n", line == 1 ? newf : exst, "$F"); 80 | } 81 | fprintf(stdout, "\n"); 82 | 83 | fclose(f); 84 | } 85 | -------------------------------------------------------------------------------- /uget.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define ERR_GENERAL 1 15 | #define ERR_SOCKET 2 16 | #define ERR_GETADDRINFO 3 17 | #define ERR_CONNECT 4 18 | #define ERR_SEND 5 19 | #define ERR_USAGE 6 20 | 21 | #define NDEBUG 22 | 23 | int get_http_respcode(const char *inpbuf) { 24 | char proto[4096], descr[4096]; 25 | int code; 26 | 27 | if (sscanf(inpbuf, "%s %d %s", proto, &code, descr) < 2) 28 | return -1; 29 | return code; 30 | } 31 | 32 | int download(int writefd, char *hostname, char *uri) { 33 | int ret = ERR_GENERAL; 34 | 35 | struct addrinfo hints, *res, *res0; 36 | 37 | memset(&hints, 0, sizeof(hints)); 38 | hints.ai_family = PF_UNSPEC; 39 | hints.ai_socktype = SOCK_STREAM; 40 | int err = getaddrinfo(hostname, "80", &hints, &res0); 41 | if (err) { 42 | #ifndef NDEBUG 43 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); 44 | #endif 45 | return ERR_GETADDRINFO; 46 | } 47 | 48 | int s = -1; 49 | for (res = res0; res; res = res->ai_next) { 50 | s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 51 | if (s < 0) { 52 | ret = ERR_SOCKET; 53 | continue; 54 | } 55 | 56 | #ifndef NDEBUG 57 | char buf[256]; 58 | inet_ntop(res->ai_family, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 59 | buf, sizeof(buf)); 60 | fprintf(stderr, "Connecting to %s...\n", buf); 61 | #endif 62 | 63 | if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { 64 | ret = ERR_CONNECT; 65 | close(s); 66 | s = -1; 67 | continue; 68 | } 69 | break; /* okay we got one */ 70 | } 71 | freeaddrinfo(res0); 72 | 73 | if (s < 0) { 74 | return ret; 75 | } 76 | 77 | char buf[4096]; 78 | // use the hack to save some space in .rodata 79 | strcpy(buf, "GET /"); 80 | if (uri) { 81 | strncat(buf, uri, sizeof(buf) - strlen(buf) - 1); 82 | } 83 | strncat(buf, " HTTP/1.0\r\nHost: ", sizeof(buf) - strlen(buf) - 1); 84 | strncat(buf, hostname, sizeof(buf) - strlen(buf) - 1); 85 | strncat(buf, "\r\n\r\n", sizeof(buf) - strlen(buf) - 1); 86 | int tosent = strlen(buf); 87 | int nsent = send(s, buf, tosent, 0); 88 | if (nsent != tosent) 89 | return ERR_SEND; 90 | 91 | int header = 1; 92 | int nrecvd; 93 | while ((nrecvd = recv(s, buf, sizeof(buf), 0))) { 94 | char *ptr = buf; 95 | if (header) { 96 | ptr = strstr(buf, "\r\n\r\n"); 97 | if (!ptr) 98 | continue; 99 | 100 | int rcode = get_http_respcode(buf); 101 | if (rcode / 100 != 2) 102 | return rcode / 100 * 10 + rcode % 10; 103 | 104 | header = 0; 105 | ptr += 4; 106 | nrecvd -= ptr - buf; 107 | } 108 | write(writefd, ptr, nrecvd); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int main(int argc, char **argv) { 115 | if (argc < 2) 116 | return ERR_USAGE; 117 | 118 | int url_arg = 1; 119 | int run_program = 0; 120 | if (argc == 3) { 121 | if (strcmp("run", argv[1]) != 0) 122 | return ERR_USAGE; 123 | run_program = 1; 124 | url_arg++; 125 | } 126 | 127 | char *hostname = argv[url_arg]; 128 | char *uri = NULL; 129 | 130 | char *url = hostname; 131 | while (*url) { 132 | if (*url == '/') { 133 | *url++ = 0; 134 | uri = url; 135 | break; 136 | } 137 | url++; 138 | } 139 | 140 | int fd = STDOUT_FILENO; 141 | char temfname[] = "/tmp/ugetXXXXXX"; 142 | if (run_program) { 143 | fd = mkstemp(temfname); 144 | } 145 | 146 | int ret = download(fd, hostname, uri); 147 | if (ret) 148 | goto cleanup; 149 | 150 | if (run_program) { 151 | fchmod(fd, S_IRUSR | S_IXUSR); 152 | close(fd); 153 | int child = fork(); 154 | if (child) { 155 | int wstatus; 156 | 157 | wait(&wstatus); 158 | ret = WEXITSTATUS(wstatus); 159 | } else { 160 | execlp(temfname, temfname, (char *)NULL); 161 | return EXIT_FAILURE; 162 | } 163 | } 164 | 165 | cleanup: 166 | if (run_program) 167 | unlink(temfname); 168 | 169 | return ret; 170 | } 171 | --------------------------------------------------------------------------------