10
my $supports_sasl = supports_sasl();
14
if (supports_sasl()) {
19
my $server = new_memcached("-S");
21
ok($@, "Died with illegal -S args when SASL is not supported.");
26
my $server = new_memcached("-S -B auto");
28
ok($@, "SASL shouldn't be used with protocol auto negotiate");
31
my $server = new_memcached("-S -B ascii");
33
ok($@, "SASL isn't implemented in the ascii protocol");
36
my $server = new_memcached("-S -B binary -B ascii");
38
ok($@, "SASL isn't implemented in the ascii protocol");
40
# Based almost 100% off testClient.py which is:
41
# Copyright (c) 2007 Dustin Sallings <dustin@spy.net>
44
use constant CMD_GET => 0x00;
45
use constant CMD_SET => 0x01;
46
use constant CMD_ADD => 0x02;
47
use constant CMD_REPLACE => 0x03;
48
use constant CMD_DELETE => 0x04;
49
use constant CMD_INCR => 0x05;
50
use constant CMD_DECR => 0x06;
51
use constant CMD_QUIT => 0x07;
52
use constant CMD_FLUSH => 0x08;
53
use constant CMD_GETQ => 0x09;
54
use constant CMD_NOOP => 0x0A;
55
use constant CMD_VERSION => 0x0B;
56
use constant CMD_GETK => 0x0C;
57
use constant CMD_GETKQ => 0x0D;
58
use constant CMD_APPEND => 0x0E;
59
use constant CMD_PREPEND => 0x0F;
60
use constant CMD_STAT => 0x10;
61
use constant CMD_SETQ => 0x11;
62
use constant CMD_ADDQ => 0x12;
63
use constant CMD_REPLACEQ => 0x13;
64
use constant CMD_DELETEQ => 0x14;
65
use constant CMD_INCREMENTQ => 0x15;
66
use constant CMD_DECREMENTQ => 0x16;
67
use constant CMD_QUITQ => 0x17;
68
use constant CMD_FLUSHQ => 0x18;
69
use constant CMD_APPENDQ => 0x19;
70
use constant CMD_PREPENDQ => 0x1A;
72
use constant CMD_SASL_LIST_MECHS => 0x20;
73
use constant CMD_SASL_AUTH => 0x21;
74
use constant CMD_SASL_STEP => 0x22;
75
use constant ERR_AUTH_ERROR => 0x20;
78
# REQ and RES formats are divided even though they currently share
79
# the same format, since they _could_ differ in the future.
80
use constant REQ_PKT_FMT => "CCnCCnNNNN";
81
use constant RES_PKT_FMT => "CCnCCnNNNN";
82
use constant INCRDECR_PKT_FMT => "NNNNN";
83
use constant MIN_RECV_BYTES => length(pack(RES_PKT_FMT));
84
use constant REQ_MAGIC => 0x80;
85
use constant RES_MAGIC => 0x81;
88
$ENV{'SASL_CONF_PATH'} = "$pwd/t/sasl";
90
my $server = new_memcached('-B binary -S ');
92
my $mc = MC::Client->new;
95
my ($key, $orig_val) = @_;
96
my ($status, $val, $cas) = $mc->get($key);
98
if ($val =~ /^\d+$/) {
99
cmp_ok($val,'==', $orig_val, "$val = $orig_val");
102
cmp_ok($val, 'eq', $orig_val, "$val = $orig_val");
107
my ($key, $orig_value, $exp) = @_;
108
$exp = defined $exp ? $exp : 0;
109
my ($status, $rv)= $mc->set($key, $orig_value, $exp);
110
$check->($key, $orig_value);
115
my ($status,$rv) =()= eval { $mc->get($key) };
116
#if ($status == ERR_AUTH_ERROR) {
117
# ok($@->auth_error, "Not authorized to connect");
120
# ok($@->not_found, "We got a not found error when we expected one");
123
ok($@->not_found, "We got a not found error when we expected one");
128
my ($key, $when) = @_;
129
$mc->delete($key, $when);
134
ok($server, "started the server");
136
my $v = $mc->version;
137
ok(defined $v && length($v), "Proper version: $v");
140
my $mechs= $mc->list_mechs();
141
Test::More::cmp_ok($mechs, 'eq', 'CRAM-MD5 PLAIN', "list_mechs $mechs");
143
# this should fail, not authenticated
145
my ($status, $val)= $mc->set('x', "somevalue");
146
ok($status, "this fails to authenticate");
147
cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
151
my $mc = MC::Client->new;
152
my ($status, $val) = $mc->delete('x');
153
ok($status, "this fails to authenticate");
154
cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
158
my $mc = MC::Client->new;
159
my ($status, $val)= $mc->set('x', "somevalue");
160
ok($status, "this fails to authenticate");
161
cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
165
my $mc = MC::Client->new;
166
my ($status, $val)= $mc->flush('x');
167
ok($status, "this fails to authenticate");
168
cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
172
# Build the auth DB for testing.
173
my $sasldb = '/tmp/test-memcached.sasldb';
175
system("echo testpass | saslpasswd2 -a memcached -c -p testuser");
177
$mc = MC::Client->new;
179
# Attempt a bad auth mech.
180
is ($mc->authenticate('testuser', 'testpass', "X" x 40), 0x4, "bad mech");
182
# Attempt bad authentication.
183
is ($mc->authenticate('testuser', 'wrongpassword'), 0x20, "bad auth");
185
# Now try good authentication and make the tests work.
186
is ($mc->authenticate('testuser', 'testpass'), 0, "authenticated");
189
my ($status, $val)= $mc->set('x', "somevalue");
192
$check->('x','somevalue');
195
my ($status, $val)= $mc->delete('x');
201
my ($status, $val)= $mc->set('x', "somevalue");
204
$check->('x','somevalue');
207
my ($status, $val)= $mc->flush('x');
212
# check the SASL stats, make sure they track things correctly
213
# note: the enabled or not is presence checked in stats.t
215
# while authenticated, get current counter
217
# My initial approach was going to be to get current counts, reauthenticate
218
# and fail, followed by a reauth successfully so I'd know what happened.
219
# Reauthentication is currently unsupported, so it doesn't work that way at the
220
# moment. Adding tests may break this.
223
my %stats = $mc->stats('');
224
is ($stats{'auth_cmds'}, 2, "auth commands counted");
225
is ($stats{'auth_errors'}, 1, "auth errors correct");
229
# Along with the assertion added to the code to verify we're staying
230
# within bounds when we do a stats detail dump (detail turned on at
232
# my %stats = $mc->stats('detail dump');
234
# ######################################################################
235
# Test ends around here.
236
# ######################################################################
242
use fields qw(socket);
243
use IO::Socket::INET;
245
use constant ERR_AUTH_ERROR => 0x20;
250
$s = $server unless defined $s;
252
$self = fields::new($self);
253
$self->{socket} = $sock;
258
my ($self, $user, $pass, $mech)= @_;
260
my $buf = sprintf("%c%s%c%s", 0, $user, 0, $pass);
261
my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_AUTH, $mech, $buf, '');
266
my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_LIST_MECHS, '', '', '');
267
return join(" ", sort(split(/\s+/, $rv)));
272
die "Not enough args to send_command" unless @_ >= 4;
273
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
275
$extra_header = '' unless defined $extra_header;
276
my $keylen = length($key);
277
my $vallen = length($val);
278
my $extralen = length($extra_header);
279
my $datatype = 0; # field for future use
280
my $reserved = 0; # field for future use
281
my $totallen = $keylen + $vallen + $extralen;
286
$ident_hi = int($cas / 2 ** 32);
287
$ident_lo = int($cas % 2 ** 32);
290
my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
291
$datatype, $reserved, $totallen, $opaque, $ident_hi,
293
my $full_msg = $msg . $extra_header . $key . $val;
299
die "Not enough args to send_command" unless @_ >= 4;
300
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
302
my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
304
my $sent = $self->{socket}->send($full_msg);
305
die("Send failed: $!") unless $sent;
306
if($sent != length($full_msg)) {
307
die("only sent $sent of " . length($full_msg) . " bytes");
313
$self->{socket}->flush;
316
# Send a silent command and ensure it doesn't respond.
319
die "Not enough args to send_silent" unless @_ >= 4;
320
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
322
$self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
323
$self->send_command(::CMD_NOOP, '', '', $opaque + 1);
325
my ($ropaque, $status, $data) = $self->_handle_single_response;
326
Test::More::is($ropaque, $opaque + 1);
329
sub silent_mutation {
331
my ($cmd, $key, $value) = @_;
334
my $extra = pack "NN", 82, 0;
335
$mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
336
$check->($key, $value);
339
sub _handle_single_response {
341
my $myopaque = shift;
343
$self->{socket}->recv(my $response, ::MIN_RECV_BYTES);
345
my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
346
$opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $response);
348
return ($opaque, '', '', '', 0) if not defined $remaining;
349
return ($opaque, '', '', '', 0) if ($remaining == 0);
353
while($remaining - length($rv) > 0) {
354
$self->{socket}->recv(my $buf, $remaining - length($rv));
357
if(length($rv) != $remaining) {
358
my $found = length($rv);
359
die("Expected $remaining bytes, got $found");
362
my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
365
#die MC::Error->new($status, $rv);
368
return ($opaque, $status, $rv, $cas, $keylen);
374
my ($cmd, $key, $val, $extra_header, $cas) = @_;
376
$extra_header = '' unless defined $extra_header;
377
my $opaque = int(rand(2**32));
378
$self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
379
my (undef, $status, $rv, $rcas) = $self->_handle_single_response($opaque);
380
return ($status, $rv, $rcas);
383
sub _incrdecr_header {
385
my ($amt, $init, $exp) = @_;
387
my $amt_hi = int($amt / 2 ** 32);
388
my $amt_lo = int($amt % 2 ** 32);
390
my $init_hi = int($init / 2 ** 32);
391
my $init_lo = int($init % 2 ** 32);
393
my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
396
return $extra_header;
401
my ($cmd, $key, $amt, $init, $exp) = @_;
403
my ($status, $data, undef) = $self->_do_command($cmd, $key, '',
404
$self->_incrdecr_header($amt, $init, $exp));
406
my $header = substr $data, 0, 8, '';
407
my ($resp_hi, $resp_lo) = unpack "NN", $header;
408
my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
413
sub silent_incrdecr {
415
my ($cmd, $key, $amt, $init, $exp) = @_;
416
my $opaque = 8275753;
418
$mc->send_silent($cmd, $key, '', $opaque,
419
$mc->_incrdecr_header($amt, $init, $exp));
426
my $opaque = int(rand(2**32));
427
$self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
434
my ($op, $status, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
436
$found_key = substr($data, 0, $keylen);
437
$found_val = substr($data, $keylen);
438
$rv{$found_key} = $found_val;
442
} while($found_key ne '');
449
my ($status, $rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
451
my $header = substr $rv, 0, 4, '';
452
my $flags = unpack("N", $header);
454
return ($status, $rv);
461
for (my $i = 0; $i < @keys; $i++) {
462
$self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
465
my $terminal = @keys + 10;
466
$self->send_command(::CMD_NOOP, '', '', $terminal);
471
my ($opaque, $status, $data) = $self->_handle_single_response;
472
last if $opaque == $terminal;
474
my $header = substr $data, 0, 4, '';
475
my $flags = unpack("N", $header);
477
$return{$keys[$opaque]} = [$flags, $data];
480
return %return if wantarray;
486
return $self->_do_command(::CMD_VERSION, '', '');
491
return $self->_do_command(::CMD_FLUSH, '', '');
496
my ($key, $val, $flags, $expire) = @_;
497
my $extra_header = pack "NN", $flags, $expire;
499
return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
506
my ($key, $val, $expire) = @_;
507
$expire = defined $expire ? $expire : 0;
508
my $extra_header = pack "NN", $flags, $expire;
509
return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
512
sub _append_prepend {
514
my ($cmd, $key, $val, $cas) = @_;
515
return $self->_do_command($cmd, $key, $val, '', $cas);
520
my ($key, $val, $flags, $expire) = @_;
521
my $extra_header = pack "NN", $flags, $expire;
523
return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
529
return $self->_do_command(::CMD_DELETE, $key, '');
534
my ($key, $amt, $init, $exp) = @_;
535
$amt = 1 unless defined $amt;
536
$init = 0 unless defined $init;
537
$exp = 0 unless defined $exp;
539
return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
544
my ($key, $amt, $init, $exp) = @_;
545
$amt = 1 unless defined $amt;
546
$init = 0 unless defined $init;
547
$exp = 0 unless defined $exp;
549
return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
554
return $self->_do_command(::CMD_NOOP, '', '');
562
use constant ERR_UNKNOWN_CMD => 0x81;
563
use constant ERR_NOT_FOUND => 0x1;
564
use constant ERR_EXISTS => 0x2;
565
use constant ERR_TOO_BIG => 0x3;
566
use constant ERR_EINVAL => 0x4;
567
use constant ERR_NOT_STORED => 0x5;
568
use constant ERR_DELTA_BADVAL => 0x6;
569
use constant ERR_AUTH_ERROR => 0x20;
571
use overload '""' => sub {
573
return "Memcache Error ($self->[0]): $self->[1]";
579
my $self = bless $error, (ref $class || $class);
586
return $self->[0] == ERR_NOT_FOUND;
591
return $self->[0] == ERR_EXISTS;
596
return $self->[0] == ERR_TOO_BIG;
601
return $self->[0] == ERR_DELTA_BADVAL;
606
return $self->[0] == ERR_AUTH_ERROR;