hiredis-futureproof 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +28 -0
  3. data/Rakefile +53 -0
  4. data/ext/hiredis_ext/connection.c +611 -0
  5. data/ext/hiredis_ext/extconf.rb +48 -0
  6. data/ext/hiredis_ext/hiredis_ext.c +15 -0
  7. data/ext/hiredis_ext/hiredis_ext.h +44 -0
  8. data/ext/hiredis_ext/reader.c +124 -0
  9. data/lib/hiredis/connection.rb +10 -0
  10. data/lib/hiredis/ext/connection.rb +29 -0
  11. data/lib/hiredis/ext/reader.rb +2 -0
  12. data/lib/hiredis/reader.rb +10 -0
  13. data/lib/hiredis/ruby/connection.rb +316 -0
  14. data/lib/hiredis/ruby/reader.rb +183 -0
  15. data/lib/hiredis/version.rb +3 -0
  16. data/lib/hiredis.rb +2 -0
  17. data/vendor/hiredis/COPYING +29 -0
  18. data/vendor/hiredis/Makefile +308 -0
  19. data/vendor/hiredis/alloc.c +86 -0
  20. data/vendor/hiredis/alloc.h +91 -0
  21. data/vendor/hiredis/async.c +892 -0
  22. data/vendor/hiredis/async.h +147 -0
  23. data/vendor/hiredis/async_private.h +75 -0
  24. data/vendor/hiredis/dict.c +352 -0
  25. data/vendor/hiredis/dict.h +126 -0
  26. data/vendor/hiredis/fmacros.h +12 -0
  27. data/vendor/hiredis/hiredis.c +1173 -0
  28. data/vendor/hiredis/hiredis.h +336 -0
  29. data/vendor/hiredis/hiredis_ssl.h +127 -0
  30. data/vendor/hiredis/net.c +612 -0
  31. data/vendor/hiredis/net.h +56 -0
  32. data/vendor/hiredis/read.c +739 -0
  33. data/vendor/hiredis/read.h +129 -0
  34. data/vendor/hiredis/sds.c +1289 -0
  35. data/vendor/hiredis/sds.h +278 -0
  36. data/vendor/hiredis/sdsalloc.h +44 -0
  37. data/vendor/hiredis/sockcompat.c +248 -0
  38. data/vendor/hiredis/sockcompat.h +92 -0
  39. data/vendor/hiredis/ssl.c +526 -0
  40. data/vendor/hiredis/test.c +1387 -0
  41. data/vendor/hiredis/win32.h +56 -0
  42. metadata +128 -0
@@ -0,0 +1,526 @@
1
+ /*
2
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
4
+ * Copyright (c) 2019, Redis Labs
5
+ *
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions are met:
10
+ *
11
+ * * Redistributions of source code must retain the above copyright notice,
12
+ * this list of conditions and the following disclaimer.
13
+ * * Redistributions in binary form must reproduce the above copyright
14
+ * notice, this list of conditions and the following disclaimer in the
15
+ * documentation and/or other materials provided with the distribution.
16
+ * * Neither the name of Redis nor the names of its contributors may be used
17
+ * to endorse or promote products derived from this software without
18
+ * specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ #include "hiredis.h"
34
+ #include "async.h"
35
+
36
+ #include <assert.h>
37
+ #include <errno.h>
38
+ #include <string.h>
39
+ #ifdef _WIN32
40
+ #include <windows.h>
41
+ #else
42
+ #include <pthread.h>
43
+ #endif
44
+
45
+ #include <openssl/ssl.h>
46
+ #include <openssl/err.h>
47
+
48
+ #include "win32.h"
49
+ #include "async_private.h"
50
+ #include "hiredis_ssl.h"
51
+
52
+ void __redisSetError(redisContext *c, int type, const char *str);
53
+
54
+ struct redisSSLContext {
55
+ /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
56
+ SSL_CTX *ssl_ctx;
57
+
58
+ /* Requested SNI, or NULL */
59
+ char *server_name;
60
+ };
61
+
62
+ /* The SSL connection context is attached to SSL/TLS connections as a privdata. */
63
+ typedef struct redisSSL {
64
+ /**
65
+ * OpenSSL SSL object.
66
+ */
67
+ SSL *ssl;
68
+
69
+ /**
70
+ * SSL_write() requires to be called again with the same arguments it was
71
+ * previously called with in the event of an SSL_read/SSL_write situation
72
+ */
73
+ size_t lastLen;
74
+
75
+ /** Whether the SSL layer requires read (possibly before a write) */
76
+ int wantRead;
77
+
78
+ /**
79
+ * Whether a write was requested prior to a read. If set, the write()
80
+ * should resume whenever a read takes place, if possible
81
+ */
82
+ int pendingWrite;
83
+ } redisSSL;
84
+
85
+ /* Forward declaration */
86
+ redisContextFuncs redisContextSSLFuncs;
87
+
88
+ /**
89
+ * OpenSSL global initialization and locking handling callbacks.
90
+ * Note that this is only required for OpenSSL < 1.1.0.
91
+ */
92
+
93
+ #if OPENSSL_VERSION_NUMBER < 0x10100000L
94
+ #define HIREDIS_USE_CRYPTO_LOCKS
95
+ #endif
96
+
97
+ #ifdef HIREDIS_USE_CRYPTO_LOCKS
98
+ #ifdef _WIN32
99
+ typedef CRITICAL_SECTION sslLockType;
100
+ static void sslLockInit(sslLockType* l) {
101
+ InitializeCriticalSection(l);
102
+ }
103
+ static void sslLockAcquire(sslLockType* l) {
104
+ EnterCriticalSection(l);
105
+ }
106
+ static void sslLockRelease(sslLockType* l) {
107
+ LeaveCriticalSection(l);
108
+ }
109
+ #else
110
+ typedef pthread_mutex_t sslLockType;
111
+ static void sslLockInit(sslLockType *l) {
112
+ pthread_mutex_init(l, NULL);
113
+ }
114
+ static void sslLockAcquire(sslLockType *l) {
115
+ pthread_mutex_lock(l);
116
+ }
117
+ static void sslLockRelease(sslLockType *l) {
118
+ pthread_mutex_unlock(l);
119
+ }
120
+ #endif
121
+
122
+ static sslLockType* ossl_locks;
123
+
124
+ static void opensslDoLock(int mode, int lkid, const char *f, int line) {
125
+ sslLockType *l = ossl_locks + lkid;
126
+
127
+ if (mode & CRYPTO_LOCK) {
128
+ sslLockAcquire(l);
129
+ } else {
130
+ sslLockRelease(l);
131
+ }
132
+
133
+ (void)f;
134
+ (void)line;
135
+ }
136
+
137
+ static int initOpensslLocks(void) {
138
+ unsigned ii, nlocks;
139
+ if (CRYPTO_get_locking_callback() != NULL) {
140
+ /* Someone already set the callback before us. Don't destroy it! */
141
+ return REDIS_OK;
142
+ }
143
+ nlocks = CRYPTO_num_locks();
144
+ ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
145
+ if (ossl_locks == NULL)
146
+ return REDIS_ERR;
147
+
148
+ for (ii = 0; ii < nlocks; ii++) {
149
+ sslLockInit(ossl_locks + ii);
150
+ }
151
+ CRYPTO_set_locking_callback(opensslDoLock);
152
+ return REDIS_OK;
153
+ }
154
+ #endif /* HIREDIS_USE_CRYPTO_LOCKS */
155
+
156
+ int redisInitOpenSSL(void)
157
+ {
158
+ SSL_library_init();
159
+ #ifdef HIREDIS_USE_CRYPTO_LOCKS
160
+ initOpensslLocks();
161
+ #endif
162
+
163
+ return REDIS_OK;
164
+ }
165
+
166
+ /**
167
+ * redisSSLContext helper context destruction.
168
+ */
169
+
170
+ const char *redisSSLContextGetError(redisSSLContextError error)
171
+ {
172
+ switch (error) {
173
+ case REDIS_SSL_CTX_NONE:
174
+ return "No Error";
175
+ case REDIS_SSL_CTX_CREATE_FAILED:
176
+ return "Failed to create OpenSSL SSL_CTX";
177
+ case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
178
+ return "Client cert and key must both be specified or skipped";
179
+ case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
180
+ return "Failed to load CA Certificate or CA Path";
181
+ case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
182
+ return "Failed to load client certificate";
183
+ case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
184
+ return "Failed to load private key";
185
+ default:
186
+ return "Unknown error code";
187
+ }
188
+ }
189
+
190
+ void redisFreeSSLContext(redisSSLContext *ctx)
191
+ {
192
+ if (!ctx)
193
+ return;
194
+
195
+ if (ctx->server_name) {
196
+ hi_free(ctx->server_name);
197
+ ctx->server_name = NULL;
198
+ }
199
+
200
+ if (ctx->ssl_ctx) {
201
+ SSL_CTX_free(ctx->ssl_ctx);
202
+ ctx->ssl_ctx = NULL;
203
+ }
204
+
205
+ hi_free(ctx);
206
+ }
207
+
208
+
209
+ /**
210
+ * redisSSLContext helper context initialization.
211
+ */
212
+
213
+ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
214
+ const char *cert_filename, const char *private_key_filename,
215
+ const char *server_name, redisSSLContextError *error)
216
+ {
217
+ redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
218
+ if (ctx == NULL)
219
+ goto error;
220
+
221
+ ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
222
+ if (!ctx->ssl_ctx) {
223
+ if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
224
+ goto error;
225
+ }
226
+
227
+ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
228
+ SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
229
+
230
+ if ((cert_filename != NULL && private_key_filename == NULL) ||
231
+ (private_key_filename != NULL && cert_filename == NULL)) {
232
+ if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
233
+ goto error;
234
+ }
235
+
236
+ if (capath || cacert_filename) {
237
+ if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
238
+ if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
239
+ goto error;
240
+ }
241
+ }
242
+
243
+ if (cert_filename) {
244
+ if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
245
+ if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
246
+ goto error;
247
+ }
248
+ if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
249
+ if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
250
+ goto error;
251
+ }
252
+ }
253
+
254
+ if (server_name)
255
+ ctx->server_name = hi_strdup(server_name);
256
+
257
+ return ctx;
258
+
259
+ error:
260
+ redisFreeSSLContext(ctx);
261
+ return NULL;
262
+ }
263
+
264
+ /**
265
+ * SSL Connection initialization.
266
+ */
267
+
268
+
269
+ static int redisSSLConnect(redisContext *c, SSL *ssl) {
270
+ if (c->privctx) {
271
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
272
+ return REDIS_ERR;
273
+ }
274
+
275
+ redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
276
+ if (rssl == NULL) {
277
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
278
+ return REDIS_ERR;
279
+ }
280
+
281
+ c->funcs = &redisContextSSLFuncs;
282
+ rssl->ssl = ssl;
283
+
284
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
285
+ SSL_set_fd(rssl->ssl, c->fd);
286
+ SSL_set_connect_state(rssl->ssl);
287
+
288
+ ERR_clear_error();
289
+ int rv = SSL_connect(rssl->ssl);
290
+ if (rv == 1) {
291
+ c->privctx = rssl;
292
+ return REDIS_OK;
293
+ }
294
+
295
+ rv = SSL_get_error(rssl->ssl, rv);
296
+ if (((c->flags & REDIS_BLOCK) == 0) &&
297
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
298
+ c->privctx = rssl;
299
+ return REDIS_OK;
300
+ }
301
+
302
+ if (c->err == 0) {
303
+ char err[512];
304
+ if (rv == SSL_ERROR_SYSCALL)
305
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
306
+ else {
307
+ unsigned long e = ERR_peek_last_error();
308
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
309
+ ERR_reason_error_string(e));
310
+ }
311
+ __redisSetError(c, REDIS_ERR_IO, err);
312
+ }
313
+
314
+ hi_free(rssl);
315
+ return REDIS_ERR;
316
+ }
317
+
318
+ /**
319
+ * A wrapper around redisSSLConnect() for users who manage their own context and
320
+ * create their own SSL object.
321
+ */
322
+
323
+ int redisInitiateSSL(redisContext *c, SSL *ssl) {
324
+ return redisSSLConnect(c, ssl);
325
+ }
326
+
327
+ /**
328
+ * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
329
+ * manage their own SSL objects.
330
+ */
331
+
332
+ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
333
+ {
334
+ if (!c || !redis_ssl_ctx)
335
+ return REDIS_ERR;
336
+
337
+ /* We want to verify that redisSSLConnect() won't fail on this, as it will
338
+ * not own the SSL object in that case and we'll end up leaking.
339
+ */
340
+ if (c->privctx)
341
+ return REDIS_ERR;
342
+
343
+ SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
344
+ if (!ssl) {
345
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
346
+ goto error;
347
+ }
348
+
349
+ if (redis_ssl_ctx->server_name) {
350
+ if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
351
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
352
+ goto error;
353
+ }
354
+ }
355
+
356
+ return redisSSLConnect(c, ssl);
357
+
358
+ error:
359
+ if (ssl)
360
+ SSL_free(ssl);
361
+ return REDIS_ERR;
362
+ }
363
+
364
+ static int maybeCheckWant(redisSSL *rssl, int rv) {
365
+ /**
366
+ * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
367
+ * and true is returned. False is returned otherwise
368
+ */
369
+ if (rv == SSL_ERROR_WANT_READ) {
370
+ rssl->wantRead = 1;
371
+ return 1;
372
+ } else if (rv == SSL_ERROR_WANT_WRITE) {
373
+ rssl->pendingWrite = 1;
374
+ return 1;
375
+ } else {
376
+ return 0;
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Implementation of redisContextFuncs for SSL connections.
382
+ */
383
+
384
+ static void redisSSLFree(void *privctx){
385
+ redisSSL *rsc = privctx;
386
+
387
+ if (!rsc) return;
388
+ if (rsc->ssl) {
389
+ SSL_free(rsc->ssl);
390
+ rsc->ssl = NULL;
391
+ }
392
+ hi_free(rsc);
393
+ }
394
+
395
+ static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
396
+ redisSSL *rssl = c->privctx;
397
+
398
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
399
+ if (nread > 0) {
400
+ return nread;
401
+ } else if (nread == 0) {
402
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
403
+ return -1;
404
+ } else {
405
+ int err = SSL_get_error(rssl->ssl, nread);
406
+ if (c->flags & REDIS_BLOCK) {
407
+ /**
408
+ * In blocking mode, we should never end up in a situation where
409
+ * we get an error without it being an actual error, except
410
+ * in the case of EINTR, which can be spuriously received from
411
+ * debuggers or whatever.
412
+ */
413
+ if (errno == EINTR) {
414
+ return 0;
415
+ } else {
416
+ const char *msg = NULL;
417
+ if (errno == EAGAIN) {
418
+ msg = "Resource temporarily unavailable";
419
+ }
420
+ __redisSetError(c, REDIS_ERR_IO, msg);
421
+ return -1;
422
+ }
423
+ }
424
+
425
+ /**
426
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
427
+ */
428
+ if (maybeCheckWant(rssl, err)) {
429
+ return 0;
430
+ } else {
431
+ __redisSetError(c, REDIS_ERR_IO, NULL);
432
+ return -1;
433
+ }
434
+ }
435
+ }
436
+
437
+ static ssize_t redisSSLWrite(redisContext *c) {
438
+ redisSSL *rssl = c->privctx;
439
+
440
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
441
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
442
+
443
+ if (rv > 0) {
444
+ rssl->lastLen = 0;
445
+ } else if (rv < 0) {
446
+ rssl->lastLen = len;
447
+
448
+ int err = SSL_get_error(rssl->ssl, rv);
449
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
450
+ return 0;
451
+ } else {
452
+ __redisSetError(c, REDIS_ERR_IO, NULL);
453
+ return -1;
454
+ }
455
+ }
456
+ return rv;
457
+ }
458
+
459
+ static void redisSSLAsyncRead(redisAsyncContext *ac) {
460
+ int rv;
461
+ redisSSL *rssl = ac->c.privctx;
462
+ redisContext *c = &ac->c;
463
+
464
+ rssl->wantRead = 0;
465
+
466
+ if (rssl->pendingWrite) {
467
+ int done;
468
+
469
+ /* This is probably just a write event */
470
+ rssl->pendingWrite = 0;
471
+ rv = redisBufferWrite(c, &done);
472
+ if (rv == REDIS_ERR) {
473
+ __redisAsyncDisconnect(ac);
474
+ return;
475
+ } else if (!done) {
476
+ _EL_ADD_WRITE(ac);
477
+ }
478
+ }
479
+
480
+ rv = redisBufferRead(c);
481
+ if (rv == REDIS_ERR) {
482
+ __redisAsyncDisconnect(ac);
483
+ } else {
484
+ _EL_ADD_READ(ac);
485
+ redisProcessCallbacks(ac);
486
+ }
487
+ }
488
+
489
+ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
490
+ int rv, done = 0;
491
+ redisSSL *rssl = ac->c.privctx;
492
+ redisContext *c = &ac->c;
493
+
494
+ rssl->pendingWrite = 0;
495
+ rv = redisBufferWrite(c, &done);
496
+ if (rv == REDIS_ERR) {
497
+ __redisAsyncDisconnect(ac);
498
+ return;
499
+ }
500
+
501
+ if (!done) {
502
+ if (rssl->wantRead) {
503
+ /* Need to read-before-write */
504
+ rssl->pendingWrite = 1;
505
+ _EL_DEL_WRITE(ac);
506
+ } else {
507
+ /* No extra reads needed, just need to write more */
508
+ _EL_ADD_WRITE(ac);
509
+ }
510
+ } else {
511
+ /* Already done! */
512
+ _EL_DEL_WRITE(ac);
513
+ }
514
+
515
+ /* Always reschedule a read */
516
+ _EL_ADD_READ(ac);
517
+ }
518
+
519
+ redisContextFuncs redisContextSSLFuncs = {
520
+ .free_privctx = redisSSLFree,
521
+ .async_read = redisSSLAsyncRead,
522
+ .async_write = redisSSLAsyncWrite,
523
+ .read = redisSSLRead,
524
+ .write = redisSSLWrite
525
+ };
526
+