mallcop 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.md +48 -0
- data/ext/mallcop/channel.c +222 -0
- data/ext/mallcop/extconf.rb +16 -0
- data/ext/mallcop/mallcop.c +33 -0
- data/ext/mallcop/mallcop.h +31 -0
- data/ext/mallcop/session.c +228 -0
- data/lib/mallcop.rb +48 -0
- data/lib/mallcop/channel.rb +93 -0
- data/lib/mallcop/session.rb +107 -0
- data/lib/mallcop/shell.rb +49 -0
- metadata +81 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Carl Lerche, Aaron Patterson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= mall_cop
|
2
|
+
|
3
|
+
* FIX (url)
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2010 FIX
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,222 @@
|
|
1
|
+
#include <mallcop.h>
|
2
|
+
|
3
|
+
VALUE rb_cMallCopChannel;
|
4
|
+
|
5
|
+
static void dealloc(MallCopChannel *m_channel)
|
6
|
+
{
|
7
|
+
BLOCK(libssh2_channel_free( m_channel->libssh2_channel ));
|
8
|
+
mallcop_session_release( m_channel->m_session );
|
9
|
+
free( m_channel );
|
10
|
+
}
|
11
|
+
|
12
|
+
static VALUE allocate(VALUE klass)
|
13
|
+
{
|
14
|
+
MallCopChannel *m_channel = calloc( 1, sizeof(MallCopChannel) );
|
15
|
+
return Data_Wrap_Struct(klass, 0, dealloc, m_channel);
|
16
|
+
}
|
17
|
+
|
18
|
+
static VALUE initialize(VALUE self)
|
19
|
+
{
|
20
|
+
MallCopChannel *m_channel;
|
21
|
+
MallCopSession *m_session;
|
22
|
+
VALUE session;
|
23
|
+
|
24
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
25
|
+
|
26
|
+
if ( m_channel->libssh2_channel ) {
|
27
|
+
rb_raise(rb_eRuntimeError, "the libssh2 channel is already initialized");
|
28
|
+
return Qnil;
|
29
|
+
}
|
30
|
+
|
31
|
+
session = rb_iv_get(self, "@session");
|
32
|
+
|
33
|
+
if ( NIL_P(session) ) {
|
34
|
+
rb_raise(rb_eRuntimeError, "@session is nil");
|
35
|
+
return Qfalse;
|
36
|
+
}
|
37
|
+
|
38
|
+
Data_Get_Struct(session, MallCopSession, m_session);
|
39
|
+
|
40
|
+
m_channel->libssh2_channel = libssh2_channel_open_session(m_session->libssh2_session);
|
41
|
+
|
42
|
+
if ( !m_channel->libssh2_channel ) {
|
43
|
+
if ( libssh2_session_last_errno(m_session->libssh2_session) == LIBSSH2_ERROR_EAGAIN ) {
|
44
|
+
return INT2FIX(LIBSSH2_ERROR_EAGAIN);
|
45
|
+
}
|
46
|
+
|
47
|
+
mallcop_raise_last_error(m_session, rb_eRuntimeError);
|
48
|
+
return Qnil;
|
49
|
+
}
|
50
|
+
|
51
|
+
// Initializing the libssh2 channel is successful, so we can associate
|
52
|
+
// the session object with the channel object.
|
53
|
+
m_channel->m_session = m_session;
|
54
|
+
mallcop_session_retain(m_session);
|
55
|
+
|
56
|
+
return Qtrue;
|
57
|
+
}
|
58
|
+
|
59
|
+
static VALUE request_pty(VALUE self, VALUE term)
|
60
|
+
{
|
61
|
+
MallCopChannel *m_channel;
|
62
|
+
int ret;
|
63
|
+
|
64
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
65
|
+
|
66
|
+
ret = libssh2_channel_request_pty(m_channel->libssh2_channel, StringValuePtr(term));
|
67
|
+
|
68
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
69
|
+
return INT2FIX(ret);
|
70
|
+
}
|
71
|
+
|
72
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
73
|
+
return Qnil;
|
74
|
+
}
|
75
|
+
|
76
|
+
static VALUE shell(VALUE self)
|
77
|
+
{
|
78
|
+
MallCopChannel *m_channel;
|
79
|
+
int ret;
|
80
|
+
|
81
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
82
|
+
|
83
|
+
ret = libssh2_channel_shell(m_channel->libssh2_channel);
|
84
|
+
|
85
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
86
|
+
return INT2FIX(ret);
|
87
|
+
}
|
88
|
+
|
89
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
90
|
+
return Qnil;
|
91
|
+
}
|
92
|
+
|
93
|
+
static VALUE channel_exec(VALUE self, VALUE command)
|
94
|
+
{
|
95
|
+
MallCopChannel *m_channel;
|
96
|
+
int ret;
|
97
|
+
|
98
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
99
|
+
|
100
|
+
ret = libssh2_channel_exec(m_channel->libssh2_channel, StringValuePtr(command));
|
101
|
+
|
102
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
103
|
+
return INT2FIX(ret);
|
104
|
+
}
|
105
|
+
|
106
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
107
|
+
return Qnil;
|
108
|
+
}
|
109
|
+
|
110
|
+
static VALUE channel_read(VALUE self)
|
111
|
+
{
|
112
|
+
MallCopChannel *m_channel;
|
113
|
+
char buffer[0x4000];
|
114
|
+
int count;
|
115
|
+
|
116
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
117
|
+
|
118
|
+
count = libssh2_channel_read(m_channel->libssh2_channel, buffer, sizeof(buffer));
|
119
|
+
|
120
|
+
if ( count >= 0 ) {
|
121
|
+
return rb_str_new((const char *) &buffer, count);
|
122
|
+
} else if ( count == LIBSSH2_ERROR_EAGAIN ) {
|
123
|
+
return INT2FIX(LIBSSH2_ERROR_EAGAIN);
|
124
|
+
}
|
125
|
+
|
126
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
127
|
+
return Qnil;
|
128
|
+
}
|
129
|
+
|
130
|
+
static VALUE channel_write(VALUE self, VALUE string)
|
131
|
+
{
|
132
|
+
MallCopChannel *m_channel;
|
133
|
+
ssize_t ret;
|
134
|
+
|
135
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
136
|
+
|
137
|
+
ret = libssh2_channel_write(m_channel->libssh2_channel, StringValuePtr(string), RSTRING_LEN(string));
|
138
|
+
|
139
|
+
if ( ret >= 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
140
|
+
return INT2FIX(ret);
|
141
|
+
}
|
142
|
+
|
143
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
144
|
+
return Qnil;
|
145
|
+
}
|
146
|
+
|
147
|
+
static VALUE channel_close(VALUE self)
|
148
|
+
{
|
149
|
+
MallCopChannel *m_channel;
|
150
|
+
int ret;
|
151
|
+
|
152
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
153
|
+
|
154
|
+
ret = libssh2_channel_close(m_channel->libssh2_channel);
|
155
|
+
|
156
|
+
if ( ret >= 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
157
|
+
return INT2FIX(ret);
|
158
|
+
}
|
159
|
+
|
160
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
161
|
+
return Qnil;
|
162
|
+
}
|
163
|
+
|
164
|
+
static VALUE eof(VALUE self)
|
165
|
+
{
|
166
|
+
MallCopChannel *m_channel;
|
167
|
+
|
168
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
169
|
+
|
170
|
+
return libssh2_channel_eof(m_channel->libssh2_channel) ? Qtrue : Qfalse;
|
171
|
+
}
|
172
|
+
|
173
|
+
static VALUE send_eof(VALUE self)
|
174
|
+
{
|
175
|
+
MallCopChannel *m_channel;
|
176
|
+
int ret;
|
177
|
+
|
178
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
179
|
+
|
180
|
+
ret = libssh2_channel_send_eof(m_channel->libssh2_channel);
|
181
|
+
|
182
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
183
|
+
return INT2FIX(ret);
|
184
|
+
}
|
185
|
+
|
186
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
187
|
+
return Qnil;
|
188
|
+
}
|
189
|
+
|
190
|
+
static VALUE flush(VALUE self)
|
191
|
+
{
|
192
|
+
MallCopChannel *m_channel;
|
193
|
+
int ret;
|
194
|
+
|
195
|
+
Data_Get_Struct(self, MallCopChannel, m_channel);
|
196
|
+
|
197
|
+
ret = libssh2_channel_flush(m_channel->libssh2_channel);
|
198
|
+
|
199
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
200
|
+
return INT2FIX(ret);
|
201
|
+
}
|
202
|
+
|
203
|
+
mallcop_raise_last_error(m_channel->m_session, rb_eRuntimeError);
|
204
|
+
return Qnil;
|
205
|
+
}
|
206
|
+
|
207
|
+
void init_mallcop_channel()
|
208
|
+
{
|
209
|
+
rb_cMallCopChannel = rb_define_class_under(rb_mMallCop, "Channel", rb_cObject);
|
210
|
+
rb_define_alloc_func(rb_cMallCopChannel, allocate);
|
211
|
+
|
212
|
+
rb_define_private_method(rb_cMallCopChannel, "native_eof?", eof, 0);
|
213
|
+
rb_define_private_method(rb_cMallCopChannel, "native_initialize", initialize, 0);
|
214
|
+
rb_define_private_method(rb_cMallCopChannel, "native_channel_close", channel_close, 0);
|
215
|
+
rb_define_private_method(rb_cMallCopChannel, "native_channel_read", channel_read, 0);
|
216
|
+
rb_define_private_method(rb_cMallCopChannel, "native_channel_write", channel_write, 1);
|
217
|
+
rb_define_private_method(rb_cMallCopChannel, "native_request_pty", request_pty, 1);
|
218
|
+
rb_define_private_method(rb_cMallCopChannel, "native_shell", shell, 0);
|
219
|
+
rb_define_private_method(rb_cMallCopChannel, "native_channel_exec", channel_exec, 1);
|
220
|
+
rb_define_private_method(rb_cMallCopChannel, "native_send_eof", send_eof, 0);
|
221
|
+
rb_define_private_method(rb_cMallCopChannel, "native_flush", flush, 0);
|
222
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
# :stopdoc:
|
4
|
+
|
5
|
+
dir_config 'libssh2', '/opt/local/include', '/opt/local/lib'
|
6
|
+
|
7
|
+
def asplode missing
|
8
|
+
abort "#{missing} is missing. Please install libssh2."
|
9
|
+
end
|
10
|
+
|
11
|
+
asplode('libssh2.h') unless find_header 'libssh2.h'
|
12
|
+
asplode('libssh2') unless find_library 'ssh2', 'libssh2_session_init_ex'
|
13
|
+
|
14
|
+
create_makefile 'mallcop/mallcop'
|
15
|
+
|
16
|
+
# :startdoc:
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#include <mallcop.h>
|
2
|
+
|
3
|
+
VALUE rb_mMallCop;
|
4
|
+
|
5
|
+
void mallcop_raise_last_error(MallCopSession *m_session, VALUE klass)
|
6
|
+
{
|
7
|
+
char *errmsg;
|
8
|
+
|
9
|
+
libssh2_session_last_error(m_session->libssh2_session, &errmsg, NULL, 0);
|
10
|
+
|
11
|
+
rb_raise(klass, errmsg);
|
12
|
+
}
|
13
|
+
|
14
|
+
void Init_mallcop()
|
15
|
+
{
|
16
|
+
int rc;
|
17
|
+
|
18
|
+
rb_mMallCop = rb_define_module("MallCop");
|
19
|
+
|
20
|
+
rb_define_const(rb_mMallCop, "ERROR_ALLOC", INT2FIX(LIBSSH2_ERROR_ALLOC));
|
21
|
+
rb_define_const(rb_mMallCop, "ERROR_BANNER_SEND", INT2FIX(LIBSSH2_ERROR_BANNER_SEND));
|
22
|
+
rb_define_const(rb_mMallCop, "ERROR_CHANNEL_FAILURE", INT2FIX(LIBSSH2_ERROR_CHANNEL_FAILURE));
|
23
|
+
rb_define_const(rb_mMallCop, "ERROR_EAGAIN", INT2FIX(LIBSSH2_ERROR_EAGAIN));
|
24
|
+
rb_define_const(rb_mMallCop, "ERROR_KEX_FAILURE", INT2FIX(LIBSSH2_ERROR_KEX_FAILURE));
|
25
|
+
rb_define_const(rb_mMallCop, "ERROR_SOCKET_DISCONNECT", INT2FIX(LIBSSH2_ERROR_SOCKET_DISCONNECT));
|
26
|
+
rb_define_const(rb_mMallCop, "ERROR_SOCKET_NONE", INT2FIX(LIBSSH2_ERROR_SOCKET_NONE));
|
27
|
+
rb_define_const(rb_mMallCop, "ERROR_SOCKET_SEND", INT2FIX(LIBSSH2_ERROR_SOCKET_SEND));
|
28
|
+
rb_define_const(rb_mMallCop, "ERROR_PROTO", INT2FIX(LIBSSH2_ERROR_PROTO));
|
29
|
+
rb_define_const(rb_mMallCop, "HASH_SHA1", INT2FIX(LIBSSH2_HOSTKEY_HASH_SHA1));
|
30
|
+
|
31
|
+
init_mallcop_session();
|
32
|
+
init_mallcop_channel();
|
33
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#ifndef MALLCOP_H
|
2
|
+
#define MALLCOP_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <libssh2.h>
|
6
|
+
|
7
|
+
#define BLOCK(stmt) while ((stmt) == LIBSSH2_ERROR_EAGAIN)
|
8
|
+
|
9
|
+
extern VALUE rb_mMallCop;
|
10
|
+
extern VALUE rb_cMallCopSession;
|
11
|
+
extern VALUE rb_cMallCopChannel;
|
12
|
+
|
13
|
+
typedef struct {
|
14
|
+
LIBSSH2_SESSION *libssh2_session;
|
15
|
+
int count;
|
16
|
+
} MallCopSession;
|
17
|
+
|
18
|
+
typedef struct {
|
19
|
+
LIBSSH2_CHANNEL *libssh2_channel;
|
20
|
+
MallCopSession *m_session;
|
21
|
+
} MallCopChannel;
|
22
|
+
|
23
|
+
void init_mallcop_channel();
|
24
|
+
void init_mallcop_session();
|
25
|
+
|
26
|
+
void mallcop_session_retain(MallCopSession *session);
|
27
|
+
void mallcop_session_release(MallCopSession *session);
|
28
|
+
|
29
|
+
void mallcop_raise_last_error(MallCopSession *m_session, VALUE klass);
|
30
|
+
|
31
|
+
#endif
|
@@ -0,0 +1,228 @@
|
|
1
|
+
#include <mallcop.h>
|
2
|
+
|
3
|
+
VALUE rb_cMallCopSession;
|
4
|
+
|
5
|
+
void mallcop_session_retain(MallCopSession *m_session)
|
6
|
+
{
|
7
|
+
++m_session->count;
|
8
|
+
}
|
9
|
+
|
10
|
+
void mallcop_session_release(MallCopSession *m_session)
|
11
|
+
{
|
12
|
+
if ( --m_session->count == 0 ) {
|
13
|
+
BLOCK(libssh2_session_free( m_session->libssh2_session ));
|
14
|
+
free( m_session );
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
static void dealloc(MallCopSession *m_session)
|
19
|
+
{
|
20
|
+
mallcop_session_release( m_session );
|
21
|
+
}
|
22
|
+
|
23
|
+
static VALUE allocate(VALUE klass)
|
24
|
+
{
|
25
|
+
MallCopSession *m_session = calloc( 1, sizeof(MallCopSession) );
|
26
|
+
mallcop_session_retain(m_session);
|
27
|
+
|
28
|
+
return Data_Wrap_Struct(klass, 0, dealloc, m_session);
|
29
|
+
}
|
30
|
+
|
31
|
+
static VALUE initialize(VALUE self)
|
32
|
+
{
|
33
|
+
MallCopSession *m_session;
|
34
|
+
|
35
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
36
|
+
|
37
|
+
if ( m_session->libssh2_session ) {
|
38
|
+
rb_raise(rb_eRuntimeError, "the libssh2 session is already initialized");
|
39
|
+
return Qnil;
|
40
|
+
}
|
41
|
+
|
42
|
+
m_session->libssh2_session = libssh2_session_init();
|
43
|
+
|
44
|
+
if ( !m_session->libssh2_session ) {
|
45
|
+
rb_raise(rb_eRuntimeError, "session init failed");
|
46
|
+
return Qnil;
|
47
|
+
}
|
48
|
+
|
49
|
+
return Qtrue;
|
50
|
+
}
|
51
|
+
|
52
|
+
static VALUE set_blocking(VALUE self, VALUE blocking)
|
53
|
+
{
|
54
|
+
MallCopSession *m_session;
|
55
|
+
|
56
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
57
|
+
|
58
|
+
if ( !m_session->libssh2_session ) {
|
59
|
+
rb_raise(rb_eRuntimeError, "session not initialized yet");
|
60
|
+
return Qnil;
|
61
|
+
}
|
62
|
+
|
63
|
+
libssh2_session_set_blocking( m_session->libssh2_session, NUM2INT(blocking) );
|
64
|
+
|
65
|
+
return Qnil;
|
66
|
+
}
|
67
|
+
|
68
|
+
static VALUE start(VALUE self, VALUE r_fd)
|
69
|
+
{
|
70
|
+
MallCopSession *m_session;
|
71
|
+
int ret;
|
72
|
+
int fd = NUM2INT(r_fd);
|
73
|
+
|
74
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
75
|
+
|
76
|
+
ret = libssh2_session_startup(m_session->libssh2_session, fd);
|
77
|
+
|
78
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
79
|
+
return INT2FIX(ret);
|
80
|
+
}
|
81
|
+
|
82
|
+
mallcop_raise_last_error(m_session, rb_eRuntimeError);
|
83
|
+
return Qnil;
|
84
|
+
}
|
85
|
+
|
86
|
+
static VALUE hostkey_hash(VALUE self, VALUE hashtype)
|
87
|
+
{
|
88
|
+
MallCopSession *m_session;
|
89
|
+
const char *key;
|
90
|
+
int keytype = NUM2INT(hashtype);
|
91
|
+
|
92
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
93
|
+
|
94
|
+
key = libssh2_hostkey_hash(m_session->libssh2_session, keytype);
|
95
|
+
|
96
|
+
if ( !key ) {
|
97
|
+
return Qnil;
|
98
|
+
}
|
99
|
+
|
100
|
+
if ( keytype == LIBSSH2_HOSTKEY_HASH_SHA1 ) {
|
101
|
+
return rb_str_new(key, 20);
|
102
|
+
}
|
103
|
+
|
104
|
+
return rb_str_new(key, 16);
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE userauth_list(VALUE self, VALUE user)
|
108
|
+
{
|
109
|
+
MallCopSession *m_session;
|
110
|
+
char * list;
|
111
|
+
|
112
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
113
|
+
|
114
|
+
do {
|
115
|
+
list = libssh2_userauth_list(m_session->libssh2_session,
|
116
|
+
StringValuePtr(user), RSTRING_LEN(user));
|
117
|
+
} while ( !list && libssh2_session_last_errno(m_session->libssh2_session) == LIBSSH2_ERROR_EAGAIN );
|
118
|
+
|
119
|
+
return rb_str_new2(list);
|
120
|
+
}
|
121
|
+
|
122
|
+
static VALUE userauth_password(VALUE self, VALUE user, VALUE password)
|
123
|
+
{
|
124
|
+
MallCopSession *m_session;
|
125
|
+
int ret;
|
126
|
+
|
127
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
128
|
+
|
129
|
+
ret = libssh2_userauth_password(m_session->libssh2_session,
|
130
|
+
StringValuePtr(user), StringValuePtr(password));
|
131
|
+
|
132
|
+
if ( ret == 0 || ret == LIBSSH2_ERROR_EAGAIN ) {
|
133
|
+
return INT2FIX(ret);
|
134
|
+
}
|
135
|
+
|
136
|
+
ret = libssh2_session_last_errno(m_session->libssh2_session);
|
137
|
+
|
138
|
+
switch (ret) {
|
139
|
+
case LIBSSH2_ERROR_PASSWORD_EXPIRED:
|
140
|
+
case LIBSSH2_ERROR_AUTHENTICATION_FAILED:
|
141
|
+
return INT2FIX(ret);
|
142
|
+
break;
|
143
|
+
}
|
144
|
+
|
145
|
+
mallcop_raise_last_error(m_session, rb_eRuntimeError);
|
146
|
+
return Qnil;
|
147
|
+
}
|
148
|
+
|
149
|
+
static VALUE userauth_publickey_fromfile(VALUE self, VALUE user, VALUE public_key, VALUE private_key, VALUE password)
|
150
|
+
{
|
151
|
+
MallCopSession *m_session;
|
152
|
+
int ret;
|
153
|
+
|
154
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
155
|
+
|
156
|
+
BLOCK(ret = libssh2_userauth_publickey_fromfile(m_session->libssh2_session,
|
157
|
+
StringValuePtr(user), StringValuePtr(public_key),
|
158
|
+
StringValuePtr(private_key), StringValuePtr(password)));
|
159
|
+
|
160
|
+
if(ret) {
|
161
|
+
switch(ret) {
|
162
|
+
case LIBSSH2_ERROR_AUTHENTICATION_FAILED:
|
163
|
+
case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED:
|
164
|
+
return Qfalse;
|
165
|
+
break;
|
166
|
+
default:
|
167
|
+
rb_raise(rb_eRuntimeError, "authentication failed (%d)", ret);
|
168
|
+
break;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
return Qtrue;
|
173
|
+
}
|
174
|
+
|
175
|
+
static VALUE disconnect(VALUE self, VALUE description)
|
176
|
+
{
|
177
|
+
MallCopSession *m_session;
|
178
|
+
int ret;
|
179
|
+
|
180
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
181
|
+
|
182
|
+
BLOCK(ret = libssh2_session_disconnect(m_session->libssh2_session,
|
183
|
+
StringValuePtr(description)));
|
184
|
+
|
185
|
+
return INT2NUM(ret);
|
186
|
+
}
|
187
|
+
|
188
|
+
static VALUE last_errno(VALUE self)
|
189
|
+
{
|
190
|
+
MallCopSession *m_session;
|
191
|
+
int ret;
|
192
|
+
|
193
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
194
|
+
|
195
|
+
ret = libssh2_session_last_errno(m_session->libssh2_session);
|
196
|
+
|
197
|
+
return INT2FIX(ret);
|
198
|
+
}
|
199
|
+
|
200
|
+
static VALUE last_errmsg(VALUE self)
|
201
|
+
{
|
202
|
+
MallCopSession *m_session;
|
203
|
+
char *errmsg;
|
204
|
+
int errlen;
|
205
|
+
|
206
|
+
Data_Get_Struct(self, MallCopSession, m_session);
|
207
|
+
|
208
|
+
libssh2_session_last_error(m_session->libssh2_session, &errmsg, &errlen, 0);
|
209
|
+
|
210
|
+
return rb_str_new(errmsg, (long) errlen);
|
211
|
+
}
|
212
|
+
|
213
|
+
void init_mallcop_session()
|
214
|
+
{
|
215
|
+
rb_cMallCopSession = rb_define_class_under(rb_mMallCop, "Session", rb_cObject);
|
216
|
+
rb_define_alloc_func(rb_cMallCopSession, allocate);
|
217
|
+
|
218
|
+
rb_define_private_method(rb_cMallCopSession, "native_initialize", initialize, 0);
|
219
|
+
rb_define_private_method(rb_cMallCopSession, "native_start", start, 1);
|
220
|
+
rb_define_private_method(rb_cMallCopSession, "native_set_blocking", set_blocking, 1);
|
221
|
+
rb_define_private_method(rb_cMallCopSession, "native_userauth_list", userauth_list, 1);
|
222
|
+
rb_define_private_method(rb_cMallCopSession, "native_last_errno", last_errno, 0);
|
223
|
+
rb_define_private_method(rb_cMallCopSession, "native_last_errmsg", last_errmsg, 0);
|
224
|
+
rb_define_private_method(rb_cMallCopSession, "native_userauth_password", userauth_password, 2);
|
225
|
+
rb_define_private_method(rb_cMallCopSession, "native_hostkey_hash", hostkey_hash, 1);
|
226
|
+
rb_define_private_method(rb_cMallCopSession, "native_userauth_publickey_fromfile", userauth_publickey_fromfile, 4);
|
227
|
+
rb_define_private_method(rb_cMallCopSession, "native_disconnect", disconnect, 1);
|
228
|
+
}
|
data/lib/mallcop.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'mallcop/mallcop'
|
2
|
+
require 'mallcop/channel'
|
3
|
+
require 'mallcop/session'
|
4
|
+
require 'mallcop/shell'
|
5
|
+
|
6
|
+
module MallCop
|
7
|
+
VERSION = '1.0.0'
|
8
|
+
|
9
|
+
# Raised when something happens with connecting to the server
|
10
|
+
class RuntimeError < ::RuntimeError; end
|
11
|
+
class ConnectionError < StandardError ; end
|
12
|
+
class SessionError < StandardError ; end
|
13
|
+
class ChannelError < StandardError ; end
|
14
|
+
|
15
|
+
# Establishes a connection to the SSH server.
|
16
|
+
#
|
17
|
+
# Arguments
|
18
|
+
# =====
|
19
|
+
# +host+ The host to connect to
|
20
|
+
# +username+ The username to connect under.
|
21
|
+
#
|
22
|
+
# Optional:
|
23
|
+
# +options+
|
24
|
+
# :port The port to connect on (defaults to 22)
|
25
|
+
# :password Use a password based authentication strategy.
|
26
|
+
def self.connect(host, username, options = {})
|
27
|
+
session = Session.start(host, options[:port] || 22)
|
28
|
+
|
29
|
+
unless session.authenticate username, options
|
30
|
+
raise ChannelError, "Could not authenticate user '#{username}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
if block_given?
|
34
|
+
yield session
|
35
|
+
session.disconnect("Closing session")
|
36
|
+
end
|
37
|
+
|
38
|
+
session
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.shell(host, username, options = {})
|
42
|
+
connect(host, username, options) do |session|
|
43
|
+
shell = Shell.new(session)
|
44
|
+
yield shell
|
45
|
+
shell.cleanup
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module MallCop
|
2
|
+
class Channel
|
3
|
+
|
4
|
+
def initialize(session)
|
5
|
+
@session, @open = session, true
|
6
|
+
|
7
|
+
block { native_initialize }
|
8
|
+
end
|
9
|
+
|
10
|
+
def read
|
11
|
+
retval = ERROR_EAGAIN
|
12
|
+
|
13
|
+
until retval != ERROR_EAGAIN
|
14
|
+
@session.io_select
|
15
|
+
retval = read_nonblock
|
16
|
+
end
|
17
|
+
|
18
|
+
retval
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_nonblock
|
22
|
+
raise ChannelError, "The channel has been closed" unless @open
|
23
|
+
native_channel_read
|
24
|
+
end
|
25
|
+
|
26
|
+
def write(cmd)
|
27
|
+
retval = ERROR_EAGAIN
|
28
|
+
|
29
|
+
until retval != ERROR_EAGAIN
|
30
|
+
@session.io_select(true)
|
31
|
+
retval = write_nonblock(cmd)
|
32
|
+
end
|
33
|
+
|
34
|
+
retval
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_nonblock(cmd)
|
38
|
+
raise ChannelError, "The channel has been closed" unless @open
|
39
|
+
native_channel_write(cmd)
|
40
|
+
end
|
41
|
+
|
42
|
+
def eof?
|
43
|
+
native_eof?
|
44
|
+
end
|
45
|
+
|
46
|
+
def close
|
47
|
+
return true unless @open
|
48
|
+
block { native_channel_close }
|
49
|
+
true
|
50
|
+
ensure
|
51
|
+
@open = false
|
52
|
+
end
|
53
|
+
|
54
|
+
def request_pty(term)
|
55
|
+
block { native_request_pty(term) }
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def shell
|
60
|
+
block { native_shell }
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def channel_exec(cmd)
|
65
|
+
block { native_channel_exec(cmd) }
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def send_eof
|
70
|
+
block { native_send_eof }
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def flush
|
75
|
+
block { native_flush }
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def block
|
82
|
+
retval = yield
|
83
|
+
|
84
|
+
until retval != ERROR_EAGAIN
|
85
|
+
@session.io_select
|
86
|
+
retval = yield
|
87
|
+
end
|
88
|
+
|
89
|
+
retval
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module MallCop
|
4
|
+
class Session
|
5
|
+
|
6
|
+
def self.start(host, port)
|
7
|
+
session = new(host, port)
|
8
|
+
session.start
|
9
|
+
session
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(host, port)
|
13
|
+
native_initialize
|
14
|
+
native_set_blocking(0)
|
15
|
+
@host = host
|
16
|
+
@port = port
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
block { native_start(socket.fileno) }
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticate(username, options)
|
25
|
+
allowed = authlist_for username
|
26
|
+
|
27
|
+
attempted = []
|
28
|
+
|
29
|
+
auth_methods.each do |method|
|
30
|
+
next unless allowed.include?(method)
|
31
|
+
|
32
|
+
attempted << method
|
33
|
+
|
34
|
+
if send "authenticate_via_#{method.downcase}", username, options
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def open_channel
|
43
|
+
Channel.new(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hostkey_hash(hashtype)
|
47
|
+
native_hostkey_hash(hashtype)
|
48
|
+
end
|
49
|
+
|
50
|
+
def userauth_publickey_fromfile(user, public_key, private_key, password)
|
51
|
+
native_userauth_publickey_fromfile(user, public_key, private_key, password)
|
52
|
+
end
|
53
|
+
|
54
|
+
def disconnect(description)
|
55
|
+
native_disconnect(description)
|
56
|
+
end
|
57
|
+
|
58
|
+
def io_select(write = false)
|
59
|
+
write ?
|
60
|
+
select([], [socket], nil, nil) :
|
61
|
+
select([socket], nil, nil, nil)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def socket
|
67
|
+
@socket ||= Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0).tap do |s|
|
68
|
+
s.connect Socket.sockaddr_in(@port, @host)
|
69
|
+
end
|
70
|
+
rescue Errno::ECONNREFUSED => e
|
71
|
+
raise ConnectionError, "Cannot establish a connection to #{@host} on port #{@port}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def auth_methods
|
75
|
+
%w(publickey hostbased password)
|
76
|
+
end
|
77
|
+
|
78
|
+
def authlist_for(username)
|
79
|
+
native_userauth_list(username).split ','
|
80
|
+
end
|
81
|
+
|
82
|
+
def authenticate_via_password(username, options)
|
83
|
+
return unless options[:password]
|
84
|
+
block { native_userauth_password(username, options[:password]) } == 0
|
85
|
+
end
|
86
|
+
|
87
|
+
def authenticate_via_publickey(username, options)
|
88
|
+
return unless options[:keys] && options[:keys].any?
|
89
|
+
# Not implemented yet
|
90
|
+
end
|
91
|
+
|
92
|
+
def authenticate_via_hostbased(username, options)
|
93
|
+
# Not implemented yet
|
94
|
+
end
|
95
|
+
|
96
|
+
def block
|
97
|
+
retval = yield
|
98
|
+
|
99
|
+
until retval != ERROR_EAGAIN
|
100
|
+
io_select
|
101
|
+
retval = yield
|
102
|
+
end
|
103
|
+
|
104
|
+
retval
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module MallCop
|
4
|
+
class Shell
|
5
|
+
attr_reader :channel
|
6
|
+
|
7
|
+
def initialize(session)
|
8
|
+
@session = session
|
9
|
+
@channel = session.open_channel
|
10
|
+
|
11
|
+
@channel.shell
|
12
|
+
end
|
13
|
+
|
14
|
+
def sh(cmd)
|
15
|
+
res, code = exec_with_status(cmd)
|
16
|
+
res
|
17
|
+
end
|
18
|
+
|
19
|
+
def cleanup
|
20
|
+
@channel.close
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def exec_with_status(cmd)
|
26
|
+
separator = generate_separator
|
27
|
+
# An `echo ''` is appended for now, mostly because I don't know of
|
28
|
+
# a better way to do this.
|
29
|
+
cmd = cmd.dup.strip
|
30
|
+
cmd << ";" if cmd !~ /[;&]$/
|
31
|
+
cmd << %[ echo #{separator} $?\n]
|
32
|
+
|
33
|
+
@channel.write(cmd)
|
34
|
+
|
35
|
+
res = ""
|
36
|
+
until res =~ /#{separator} (\d+)\Z/m
|
37
|
+
res << @channel.read
|
38
|
+
end
|
39
|
+
|
40
|
+
return $`.strip, $1.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_separator
|
44
|
+
s = Digest::SHA1.hexdigest([channel.object_id, object_id, Time.now.to_i, Time.now.usec, rand(0xFFFFFFFF)].join(":"))
|
45
|
+
s << Digest::SHA1.hexdigest(s)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mallcop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Carl Lerche
|
14
|
+
- Aaron Patterson
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-08-18 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies: []
|
22
|
+
|
23
|
+
description: Don't reinvent the wheel.
|
24
|
+
email:
|
25
|
+
- me@carllerche.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions:
|
29
|
+
- ext/mallcop/extconf.rb
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- ext/mallcop/extconf.rb
|
34
|
+
- ext/mallcop/channel.c
|
35
|
+
- ext/mallcop/mallcop.c
|
36
|
+
- ext/mallcop/session.c
|
37
|
+
- ext/mallcop/mallcop.h
|
38
|
+
- lib/mallcop.rb
|
39
|
+
- lib/mallcop/channel.rb
|
40
|
+
- lib/mallcop/session.rb
|
41
|
+
- lib/mallcop/shell.rb
|
42
|
+
- README.md
|
43
|
+
- LICENSE
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/carllerche/mallcop
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 23
|
68
|
+
segments:
|
69
|
+
- 1
|
70
|
+
- 3
|
71
|
+
- 6
|
72
|
+
version: 1.3.6
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: mallcop
|
76
|
+
rubygems_version: 1.3.7
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Bindings to libssh2
|
80
|
+
test_files: []
|
81
|
+
|