diff options
Diffstat (limited to 'examples/redis-unstable/src/cli_common.c')
| -rw-r--r-- | examples/redis-unstable/src/cli_common.c | 424 |
1 files changed, 0 insertions, 424 deletions
diff --git a/examples/redis-unstable/src/cli_common.c b/examples/redis-unstable/src/cli_common.c deleted file mode 100644 index 0c269de..0000000 --- a/examples/redis-unstable/src/cli_common.c +++ /dev/null @@ -1,424 +0,0 @@ -/* CLI (command line interface) common methods - * - * Copyright (c) 2020-Present, Redis Ltd. - * All rights reserved. - * - * Licensed under your choice of (a) the Redis Source Available License 2.0 - * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the - * GNU Affero General Public License v3 (AGPLv3). - */ - -#include "fmacros.h" -#include "cli_common.h" -#include "version.h" - -#include <stdio.h> -#include <stdlib.h> -#include <fcntl.h> -#include <errno.h> -#include <hiredis.h> -#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ -#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ -#include <unistd.h> -#include <string.h> -#include <ctype.h> -#ifdef USE_OPENSSL -#include <openssl/ssl.h> -#include <openssl/err.h> -#include <hiredis_ssl.h> -#endif - -#define UNUSED(V) ((void) V) - -char *redisGitSHA1(void); -char *redisGitDirty(void); - -/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if - * not building with TLS support. - */ -int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err) { -#ifdef USE_OPENSSL - static SSL_CTX *ssl_ctx = NULL; - - if (!ssl_ctx) { - ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if (!ssl_ctx) { - *err = "Failed to create SSL_CTX"; - goto error; - } - SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - SSL_CTX_set_verify(ssl_ctx, config.skip_cert_verify ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL); - - if (config.cacert || config.cacertdir) { - if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) { - *err = "Invalid CA Certificate File/Directory"; - goto error; - } - } else { - if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { - *err = "Failed to use default CA paths"; - goto error; - } - } - - if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) { - *err = "Invalid client certificate"; - goto error; - } - - if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) { - *err = "Invalid private key"; - goto error; - } - if (config.ciphers && !SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers)) { - *err = "Error while configuring ciphers"; - goto error; - } -#ifdef TLS1_3_VERSION - if (config.ciphersuites && !SSL_CTX_set_ciphersuites(ssl_ctx, config.ciphersuites)) { - *err = "Error while setting cypher suites"; - goto error; - } -#endif - } - - SSL *ssl = SSL_new(ssl_ctx); - if (!ssl) { - *err = "Failed to create SSL object"; - return REDIS_ERR; - } - - if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) { - *err = "Failed to configure SNI"; - SSL_free(ssl); - return REDIS_ERR; - } - - return redisInitiateSSL(c, ssl); - -error: - SSL_CTX_free(ssl_ctx); - ssl_ctx = NULL; - return REDIS_ERR; -#else - (void) config; - (void) c; - (void) err; - return REDIS_OK; -#endif -} - -/* Wrapper around hiredis to allow arbitrary reads and writes. - * - * We piggybacks on top of hiredis to achieve transparent TLS support, - * and use its internal buffers so it can co-exist with commands - * previously/later issued on the connection. - * - * Interface is close to enough to read()/write() so things should mostly - * work transparently. - */ - -/* Write a raw buffer through a redisContext. If we already have something - * in the buffer (leftovers from hiredis operations) it will be written - * as well. - */ -ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len) -{ - int done = 0; - - /* Append data to buffer which is *usually* expected to be empty - * but we don't assume that, and write. - */ - c->obuf = sdscatlen(c->obuf, buf, buf_len); - if (redisBufferWrite(c, &done) == REDIS_ERR) { - if (!(c->flags & REDIS_BLOCK)) - errno = EAGAIN; - - /* On error, we assume nothing was written and we roll back the - * buffer to its original state. - */ - if (sdslen(c->obuf) > buf_len) - sdsrange(c->obuf, 0, -(buf_len+1)); - else - sdsclear(c->obuf); - - return -1; - } - - /* If we're done, free up everything. We may have written more than - * buf_len (if c->obuf was not initially empty) but we don't have to - * tell. - */ - if (done) { - sdsclear(c->obuf); - return buf_len; - } - - /* Write was successful but we have some leftovers which we should - * remove from the buffer. - * - * Do we still have data that was there prior to our buf? If so, - * restore buffer to it's original state and report no new data was - * written. - */ - if (sdslen(c->obuf) > buf_len) { - sdsrange(c->obuf, 0, -(buf_len+1)); - return 0; - } - - /* At this point we're sure no prior data is left. We flush the buffer - * and report how much we've written. - */ - size_t left = sdslen(c->obuf); - sdsclear(c->obuf); - return buf_len - left; -} - -/* Wrapper around OpenSSL (libssl and libcrypto) initialisation - */ -int cliSecureInit(void) -{ -#ifdef USE_OPENSSL - ERR_load_crypto_strings(); - SSL_load_error_strings(); - SSL_library_init(); -#endif - return REDIS_OK; -} - -/* Create an sds from stdin */ -sds readArgFromStdin(void) { - char buf[1024]; - sds arg = sdsempty(); - - while(1) { - int nread = read(fileno(stdin),buf,1024); - - if (nread == 0) break; - else if (nread == -1) { - perror("Reading from standard input"); - exit(1); - } - arg = sdscatlen(arg,buf,nread); - } - return arg; -} - -/* Create an sds array from argv, either as-is or by dequoting every - * element. When quoted is non-zero, may return a NULL to indicate an - * invalid quoted string. - * - * The caller should free the resulting array of sds strings with - * sdsfreesplitres(). - */ -sds *getSdsArrayFromArgv(int argc,char **argv, int quoted) { - sds *res = sds_malloc(sizeof(sds) * argc); - - for (int j = 0; j < argc; j++) { - if (quoted) { - sds unquoted = unquoteCString(argv[j]); - if (!unquoted) { - while (--j >= 0) sdsfree(res[j]); - sds_free(res); - return NULL; - } - res[j] = unquoted; - } else { - res[j] = sdsnew(argv[j]); - } - } - - return res; -} - -/* Unquote a null-terminated string and return it as a binary-safe sds. */ -sds unquoteCString(char *str) { - int count; - sds *unquoted = sdssplitargs(str, &count); - sds res = NULL; - - if (unquoted && count == 1) { - res = unquoted[0]; - unquoted[0] = NULL; - } - - if (unquoted) - sdsfreesplitres(unquoted, count); - - return res; -} - - -/* URL-style percent decoding. */ -#define isHexChar(c) (isdigit(c) || ((c) >= 'a' && (c) <= 'f')) -#define decodeHexChar(c) (isdigit(c) ? (c) - '0' : (c) - 'a' + 10) -#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l)) - -static sds percentDecode(const char *pe, size_t len) { - const char *end = pe + len; - sds ret = sdsempty(); - const char *curr = pe; - - while (curr < end) { - if (*curr == '%') { - if ((end - curr) < 2) { - fprintf(stderr, "Incomplete URI encoding\n"); - exit(1); - } - - char h = tolower(*(++curr)); - char l = tolower(*(++curr)); - if (!isHexChar(h) || !isHexChar(l)) { - fprintf(stderr, "Illegal character in URI encoding\n"); - exit(1); - } - char c = decodeHex(h, l); - ret = sdscatlen(ret, &c, 1); - curr++; - } else { - ret = sdscatlen(ret, curr++, 1); - } - } - - return ret; -} - -/* Parse a URI and extract the server connection information. - * URI scheme is based on the provisional specification[1] excluding support - * for query parameters. Valid URIs are: - * scheme: "redis://" - * authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]] - * path: ["/" [<db>]] - * - * [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */ -void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag) { -#ifdef USE_OPENSSL - UNUSED(tool_name); -#else - UNUSED(tls_flag); -#endif - - const char *scheme = "redis://"; - const char *tlsscheme = "rediss://"; - const char *curr = uri; - const char *end = uri + strlen(uri); - const char *userinfo, *username, *port, *host, *path; - - /* URI must start with a valid scheme. */ - if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { -#ifdef USE_OPENSSL - *tls_flag = 1; - curr += strlen(tlsscheme); -#else - fprintf(stderr,"rediss:// is only supported when %s is compiled with OpenSSL\n", tool_name); - exit(1); -#endif - } else if (!strncasecmp(scheme, curr, strlen(scheme))) { - curr += strlen(scheme); - } else { - fprintf(stderr,"Invalid URI scheme\n"); - exit(1); - } - if (curr == end) return; - - /* Extract user info. */ - if ((userinfo = strchr(curr,'@'))) { - if ((username = strchr(curr, ':')) && username < userinfo) { - connInfo->user = percentDecode(curr, username - curr); - curr = username + 1; - } - - connInfo->auth = percentDecode(curr, userinfo - curr); - curr = userinfo + 1; - } - if (curr == end) return; - - /* Extract host and port. */ - path = strchr(curr, '/'); - if (*curr != '/') { - host = path ? path - 1 : end; - if (*curr == '[') { - curr += 1; - if ((port = strchr(curr, ']'))) { - if (*(port+1) == ':') { - connInfo->hostport = atoi(port + 2); - } - host = port - 1; - } - } else { - if ((port = strchr(curr, ':'))) { - connInfo->hostport = atoi(port + 1); - host = port - 1; - } - } - sdsfree(connInfo->hostip); - connInfo->hostip = sdsnewlen(curr, host - curr + 1); - } - curr = path ? path + 1 : end; - if (curr == end) return; - - /* Extract database number. */ - connInfo->input_dbnum = atoi(curr); -} - -void freeCliConnInfo(cliConnInfo connInfo){ - if (connInfo.hostip) sdsfree(connInfo.hostip); - if (connInfo.auth) sdsfree(connInfo.auth); - if (connInfo.user) sdsfree(connInfo.user); -} - -/* - * Escape a Unicode string for JSON output (--json), following RFC 7159: - * https://datatracker.ietf.org/doc/html/rfc7159#section-7 -*/ -sds escapeJsonString(sds s, const char *p, size_t len) { - s = sdscatlen(s,"\"",1); - while(len--) { - switch(*p) { - case '\\': - case '"': - s = sdscatprintf(s,"\\%c",*p); - break; - case '\n': s = sdscatlen(s,"\\n",2); break; - case '\f': s = sdscatlen(s,"\\f",2); break; - case '\r': s = sdscatlen(s,"\\r",2); break; - case '\t': s = sdscatlen(s,"\\t",2); break; - case '\b': s = sdscatlen(s,"\\b",2); break; - default: - s = sdscatprintf(s,*(unsigned char *)p <= 0x1f ? "\\u%04x" : "%c",*p); - } - p++; - } - return sdscatlen(s,"\"",1); -} - -sds cliVersion(void) { - sds version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); - - /* Add git commit and working tree status when available. */ - if (strtoll(redisGitSHA1(),NULL,16)) { - version = sdscatprintf(version, " (git:%s", redisGitSHA1()); - if (strtoll(redisGitDirty(),NULL,10)) - version = sdscatprintf(version, "-dirty"); - version = sdscat(version, ")"); - } - return version; -} - -/* This is a wrapper to call redisConnect or redisConnectWithTimeout. */ -redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv) { - if (tv.tv_sec == 0 && tv.tv_usec == 0) { - return redisConnect(ip, port); - } else { - return redisConnectWithTimeout(ip, port, tv); - } -} - -/* This is a wrapper to call redisConnectUnix or redisConnectUnixWithTimeout. */ -redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv) { - if (tv.tv_sec == 0 && tv.tv_usec == 0) { - return redisConnectUnix(path); - } else { - return redisConnectUnixWithTimeout(path, tv); - } -} |
