1 module libssh.channel;
2 
3 import core.time;
4 import std.algorithm.searching;
5 import std.algorithm.mutation;
6 
7 import libssh.c_bindings.libssh;
8 import libssh.c_bindings.server;
9 import libssh.c_bindings.callbacks;
10 import libssh.c_bindings.ctypes;
11 import libssh.errors;
12 import libssh.utils;
13 import libssh.session;
14 
15 class SSHChannelSet {
16     this() {
17     }
18 
19     void add(SSHChannel channel) {
20         if (!this._channels.canFind(channel)) {
21             this._channels ~= channel;
22         }
23     }
24 
25     void remove(SSHChannel channel) {
26         this._channels = std.algorithm.mutation.remove!(a => a == channel)(this._channels);
27     }
28 
29     bool isSet(SSHChannel channel) {
30         return this._settedChannels.canFind(channel._channel);
31     }
32 
33     void reset() {
34         this._settedChannels = [];
35     }
36 
37     private {
38         SSHChannel[] _channels = [];
39         ssh_channel[] _settedChannels = [];
40     }
41 }
42 
43 class SSHChannel : Disposable {
44     enum PollEof = int.min;
45     enum ReadAgain = int.min;
46     enum WriteAgain = int.min;
47 
48     alias OnDataCallback = uint delegate(SSHChannel channel, void[] data, bool isStdErr);
49     alias OnPtyRequestCallback = bool delegate(SSHChannel channel, string term, int width,
50         int height, int pxWidth, int pxHeight);
51     alias OnShellRequestCallback = bool delegate(SSHChannel channel);
52     alias OnPtyWindowChangeRequestCallback = bool delegate(SSHChannel channel, int width, 
53         int height, int pxWidth, int pxHeight);
54     alias OnExecRequestCallback = bool delegate(SSHChannel channel, string command);
55     alias OnEnvRequestCallback = bool delegate(SSHChannel channel, string name, string value);
56     alias OnSubsystemRequestCallback = bool delegate(SSHChannel channel, string subsystem);
57     alias OnEOFCallback = void delegate(SSHChannel channel);
58     alias OnCloseCallback = void delegate(SSHChannel channel);
59     alias OnSignalCallback = void delegate(SSHChannel channel, string signal);    // TODO: maybe signal should be enum?
60     alias OnExitStatusCallback = void delegate(SSHChannel channel, int exitStatus);
61     alias OnExitSignalCallback = void delegate(SSHChannel channel, string signal, bool isCoreDump,
62         string errMsg, string lang); // TODO: maybe signal should be enum?
63     alias OnAuthAgentRequestCallback = void delegate(SSHChannel channel);
64     alias OnX11RequestCallback = void delegate(SSHChannel channel, bool isSingleConnection, 
65         string authProtocol, string authCookie, uint screenNumber);
66 
67     @property onEOFCallback() {
68         return this._onEOFCallback;
69     }
70     
71     @property void onEOFCallback(OnEOFCallback cb) {
72         this._onEOFCallback = cb;
73         if (cb is null) {
74             this._callbacks.channel_eof_function = null;
75         } else {
76             this._callbacks.channel_eof_function = &nativeOnEOFCallback;
77         }
78         ssh_set_channel_callbacks(this._channel, &this._callbacks);
79     }
80     
81     @property onCloseCallback() {
82         return this._onCloseCallback;
83     }
84     
85     @property void onCloseCallback(OnCloseCallback cb) {
86         this._onCloseCallback = cb;
87         if (cb is null) {
88             this._callbacks.channel_close_function = null;
89         } else {
90             this._callbacks.channel_close_function = &nativeOnCloseCallback;
91         }
92         ssh_set_channel_callbacks(this._channel, &this._callbacks);
93     }
94     
95     @property onSignalCallback() {
96         return this._onSignalCallback;
97     }
98     
99     @property void onSignalCallback(OnSignalCallback cb) {
100         this._onSignalCallback = cb;
101         if (cb is null) {
102             this._callbacks.channel_signal_function = null;
103         } else {
104             this._callbacks.channel_signal_function = &nativeOnSignalCallback;
105         }
106         ssh_set_channel_callbacks(this._channel, &this._callbacks);
107     }
108     
109     @property onExitStatusCallback() {
110         return this._onExitStatusCallback;
111     }
112     
113     @property void onExitStatusCallback(OnExitStatusCallback cb) {
114         this._onExitStatusCallback = cb;
115         if (cb is null) {
116             this._callbacks.channel_exit_status_function = null;
117         } else {
118             this._callbacks.channel_exit_status_function = &nativeOnExitStatusCallback;
119         }
120         ssh_set_channel_callbacks(this._channel, &this._callbacks);
121     }
122     
123     @property onExitSignalCallback() {
124         return this._onExitSignalCallback;
125     }
126     
127     @property void onExitSignalCallback(OnExitSignalCallback cb) {
128         this._onExitSignalCallback = cb;
129         if (cb is null) {
130             this._callbacks.channel_exit_signal_function = null;
131         } else {
132             this._callbacks.channel_exit_signal_function = &nativeOnExitSignalCallback;
133         }
134         ssh_set_channel_callbacks(this._channel, &this._callbacks);
135     }
136     
137     @property onAuthAgentRequestCallback() {
138         return this._onAuthAgentRequestCallback;
139     }
140     
141     @property void onAuthAgentRequestCallback(OnAuthAgentRequestCallback cb) {
142         this._onAuthAgentRequestCallback = cb;
143         if (cb is null) {
144             this._callbacks.channel_auth_agent_req_function = null;
145         } else {
146             this._callbacks.channel_auth_agent_req_function = &nativeOnAuthAgentRequestCallback;
147         }
148         ssh_set_channel_callbacks(this._channel, &this._callbacks);
149     }
150     
151     @property onX11RequestCallback() {
152         return this._onX11RequestCallback;
153     }
154     
155     @property void onX11RequestCallback(OnX11RequestCallback cb) {
156         this._onX11RequestCallback = cb;
157         if (cb is null) {
158             this._callbacks.channel_x11_req_function = null;
159         } else {
160             this._callbacks.channel_x11_req_function = &nativeOnX11tRequestCallback;
161         }
162         ssh_set_channel_callbacks(this._channel, &this._callbacks);
163     }
164 
165     @property OnDataCallback onDataCallback() {
166         return this._onDataCallback;
167     }
168 
169     @property void onDataCallback(OnDataCallback cb) {
170         this._onDataCallback = cb;
171         if (cb is null) {
172             this._callbacks.channel_data_function = null;
173         } else {
174             this._callbacks.channel_data_function = &nativeOnData;
175         }
176         ssh_set_channel_callbacks(this._channel, &this._callbacks);
177     }
178 
179     @property OnPtyRequestCallback onPtyRequestCallback() {
180         return this._onPtyRequestCallback;
181     }
182 
183     @property void onPtyRequestCallback(OnPtyRequestCallback cb) {
184         this._onPtyRequestCallback = cb;
185         if (cb is null) {
186             this._callbacks.channel_pty_request_function = null;
187         } else {
188             this._callbacks.channel_pty_request_function = &nativeOnPtyRequestCallback;
189         }
190         ssh_set_channel_callbacks(this._channel, &this._callbacks);
191     }
192 
193     @property OnShellRequestCallback onShellRequestCallback() {
194         return this._onShellRequestCallback;
195     }
196     
197     @property void onShellRequestCallback(OnShellRequestCallback cb) {
198         this._onShellRequestCallback = cb;
199         if (cb is null) {
200             this._callbacks.channel_shell_request_function = null;
201         } else {
202             this._callbacks.channel_shell_request_function = &nativeOnShellRequestCallback;
203         }
204         ssh_set_channel_callbacks(this._channel, &this._callbacks);
205     }
206 
207     @property OnPtyWindowChangeRequestCallback onPtyWindowChangeRequestCallback() {
208         return this._onPtyWindowChangeRequestCallback;
209     }
210     
211     @property void onPtyWindowChangeRequestCallback(OnPtyWindowChangeRequestCallback cb) {
212         this._onPtyWindowChangeRequestCallback = cb;
213         if (cb is null) {
214             this._callbacks.channel_pty_window_change_function = null;
215         } else {
216             this._callbacks.channel_pty_window_change_function = &nativeOnPtyWindowChangeRequestCallback;
217         }
218         ssh_set_channel_callbacks(this._channel, &this._callbacks);
219     }
220 
221     @property OnExecRequestCallback onExecRequestCallback() {
222         return this._onExecRequestCallback;
223     }
224 
225     @property void onExecRequestCallback(OnExecRequestCallback cb) {
226         this._onExecRequestCallback = cb;
227         if (cb is null) {
228             this._callbacks.channel_exec_request_function = null;
229         } else {
230             this._callbacks.channel_exec_request_function = &nativeOnExecRequestCallback;
231         }
232         ssh_set_channel_callbacks(this._channel, &this._callbacks);
233     }
234 
235     @property OnEnvRequestCallback onEnvRequestCallback() {
236         return this._onEnvRequestCallback;
237     }
238 
239     @property void onEnvRequestCallback(OnEnvRequestCallback cb) {
240         this._onEnvRequestCallback = cb;
241         if (cb is null) {
242             this._callbacks.channel_env_request_function = null;
243         } else {
244             this._callbacks.channel_env_request_function = &nativeOnEnvRequestCallback;
245         }
246         ssh_set_channel_callbacks(this._channel, &this._callbacks);
247     }
248 
249     @property OnSubsystemRequestCallback onSubsystemRequestCallback() {
250         return this._onSubsystemRequestCallback;
251     }
252     
253     @property void onSubsystemRequestCallback(OnSubsystemRequestCallback cb) {
254         this._onSubsystemRequestCallback = cb;
255         if (cb is null) {
256             this._callbacks.channel_subsystem_request_function = null;
257         } else {
258             this._callbacks.channel_subsystem_request_function = &nativeOnSubsystemRequestCallback;
259         }
260         ssh_set_channel_callbacks(this._channel, &this._callbacks);
261     }
262 
263     @property SSHSession parent() {
264         return this._parent;
265     }
266 
267     @property int exitStatus() {
268         return ssh_channel_get_exit_status(this._channel);
269     }
270 
271     @property bool isClosed() {
272         return ssh_channel_is_closed(this._channel) == 0 ? false : true;
273     }
274 
275     @property bool isEof() {
276         return ssh_channel_is_eof(this._channel) == 0 ? false : true;
277     }
278 
279     @property bool isOpen() {
280         return ssh_channel_is_open(this._channel) == 0 ? false : true;
281     }
282 
283     @property void blocking(bool v) {
284         ssh_channel_set_blocking(this._channel, v ? 1 : 0);
285     }
286 
287     /**
288      * returns false if in nonblocking mode and call has to be done again.
289      **/
290     bool openSession() {
291         auto rc = ssh_channel_open_session(this._channel);
292         if (rc == SSH_AGAIN) {
293             return false;
294         }
295         if (rc != SSH_OK) {
296             throw new SSHException(this._parent._session);
297         }
298         return true;
299     }
300     
301     /**
302      * returns false if in nonblocking mode and call has to be done again.
303      **/
304     bool requestExec(string cmd) {
305         auto rc = ssh_channel_request_exec(this._channel, toStrZ(cmd));
306         if (rc == SSH_AGAIN) {
307             return false;
308         }
309         if (rc != SSH_OK) {
310             throw new SSHException(this._parent._session);
311         }
312         return true;
313     } 
314 
315     /**
316      * returns false if in nonblocking mode and call has to be done again.
317      **/
318     bool requestPty() {
319         auto rc = ssh_channel_request_pty(this._channel);
320         if (rc == SSH_AGAIN) {
321             return false;
322         }
323         if (rc != SSH_OK) {
324             throw new SSHException(this._parent._session);
325         }
326         return true;
327     }
328 
329     /**
330      * returns false if in nonblocking mode and call has to be done again.
331      **/
332     bool requestPtySize(string terminalType = "vt100", int width = 80, int height = 25) {
333         auto rc = ssh_channel_request_pty_size(this._channel, toStrZ(terminalType), width, height);
334         if (rc == SSH_AGAIN) {
335             return false;
336         }
337         if (rc != SSH_OK) {
338             throw new SSHException(this._parent._session);
339         }
340         return true;
341     }
342 
343     /**
344      * returns false if in nonblocking mode and call has to be done again.
345      **/
346     bool requestShell() {
347         auto rc = ssh_channel_request_shell(this._channel);
348         if (rc == SSH_AGAIN) {
349             return false;
350         }
351         if (rc != SSH_OK) {
352             throw new SSHException(this._parent._session);
353         }
354         return true;
355     }
356 
357     /**
358      * returns false if in nonblocking mode and call has to be done again.
359      **/
360     bool requestSubsystem(string subsystem) {
361         auto rc = ssh_channel_request_subsystem(this._channel, toStrZ(subsystem));
362         if (rc == SSH_AGAIN) {
363             return false;
364         }
365         if (rc != SSH_OK) {
366             throw new SSHException(this._parent._session);
367         }
368         return true;
369     }
370 
371     /**
372      * returns false if in nonblocking mode and call has to be done again.
373      **/
374     bool requestX11(bool singleConnection, string protocol, string cookie, 
375             int screenNumber) {
376         auto rc = ssh_channel_request_x11(this._channel, singleConnection ? 1 : 0,
377             toStrZ(protocol), toStrZ(cookie), screenNumber);
378         if (rc == SSH_AGAIN) {
379             return false;
380         }
381         if (rc != SSH_OK) {
382             throw new SSHException(this._parent._session);
383         }
384         return true;
385     }
386     
387     // TODO: maybe signal should be enum?
388     void sendSignal(string signal) {
389         auto rc = ssh_channel_request_send_signal(this._channel, toStrZ(signal));
390         if (rc != SSH_OK) {
391             throw new SSHException(this._parent._session);
392         }
393     }
394 
395     void sendEof() {
396         auto rc = ssh_channel_send_eof(this._channel);
397         if (rc != SSH_OK) {
398             throw new SSHException(this._parent._session);
399         }
400     }
401 
402     // TODO: maybe signal should be enum?
403     void sendExitSignal(string signal, bool core, string errMsg, string lang) {
404         auto rc = ssh_channel_request_send_exit_signal(this._channel, toStrZ(signal),
405             core ? 1 : 0, toStrZ(errMsg), toStrZ(lang));
406         if (rc != SSH_OK) {
407             throw new SSHException(this._parent._session);
408         }
409     }
410 
411     void sendExitStatus(int exitStatus) {
412         auto rc = ssh_channel_request_send_exit_status(this._channel, exitStatus);
413         if (rc != SSH_OK) {
414             throw new SSHException(this._parent._session);
415         }
416     }
417 
418     /**
419      * returns SSHChannel.PollEof if EOF
420      **/
421     int poll(bool isStdErr) {
422         auto rc = ssh_channel_poll(this._channel, isStdErr ? 1 : 0);
423         if (rc == SSH_EOF) {
424             return SSHChannel.PollEof;
425         }
426         if (rc == SSH_ERROR) {
427             throw new SSHException(this._parent._session);
428         }
429         return rc;
430     }
431 
432     /**
433      * returns SSHChannel.PollEof if EOF
434      **/
435     int pollTimeout(int timeout, bool isStdErr) {
436         auto rc = ssh_channel_poll_timeout(this._channel, timeout, isStdErr ? 1 : 0);
437         if (rc == SSH_EOF) {
438             return -1;
439         }
440         if (rc == SSH_ERROR) {
441             throw new SSHException(this._parent._session);
442         }
443         return rc;
444     }
445 
446     /**
447      * returns SSHChannel.ReadAgain in nonblocking mode
448      **/
449     int read(void[] dest, bool isStdErr) {
450         auto rc = ssh_channel_read(this._channel, dest.ptr, cast(uint) dest.length, 
451             isStdErr ? 1 : 0);
452         if (rc == SSH_ERROR) {
453             throw new SSHException(this._parent._session);
454         }
455         if (rc == SSH_AGAIN) {
456             return SSHChannel.ReadAgain;
457         }
458         return rc;
459     }
460 
461     /**
462      * returns SSHChannel.ReadAgain in nonblocking mode
463      **/
464     int readNonBlocking(void[] dest, bool isStdErr) {
465         auto rc = ssh_channel_read_nonblocking(this._channel, dest.ptr, cast(uint) dest.length, 
466             isStdErr ? 1 : 0);
467         if (rc == SSH_ERROR) {
468             throw new SSHException(this._parent._session);
469         }
470         if (rc == SSH_AGAIN) {
471             return SSHChannel.ReadAgain;
472         }
473         return rc;
474     }
475 
476     /**
477      * returns SSHChannel.ReadAgain in nonblocking mode
478      **/
479     int readTimeout(void[] dest, bool isStdErr, int timeout) {
480         auto rc = ssh_channel_read_timeout(this._channel, dest.ptr, cast(uint) dest.length, 
481             isStdErr ? 1 : 0, timeout);
482         if (rc == SSH_ERROR) {
483             throw new SSHException(this._parent._session);
484         }
485         if (rc == SSH_AGAIN) {
486             return SSHChannel.ReadAgain;
487         }
488         return rc;
489     }
490 
491     /**
492      * returns SSHChannel.WriteAgain in nonblocking mode
493      **/
494     int write(const void[] src) {
495         auto rc = ssh_channel_write(this._channel, src.ptr, cast(uint) src.length);
496         if (rc == SSH_ERROR) {
497             throw new SSHException(this._parent._session);
498         }
499         if (rc == SSH_AGAIN) {
500             return SSHChannel.WriteAgain;
501         }
502         return rc;
503     }
504 
505     /**
506      * returns SSHChannel.WriteAgain in nonblocking mode
507      **/
508     int writeStdErr(const void[] src) {
509         auto rc = ssh_channel_write_stderr(this._channel, src.ptr, cast(uint) src.length);
510         if (rc == SSH_ERROR) {
511             throw new SSHException(this._parent._session);
512         }
513         if (rc == SSH_AGAIN) {
514             return SSHChannel.WriteAgain;
515         }
516         return rc;
517     }
518 
519     /**
520      * returns false if in nonblocking mode and call has to be done again.
521      **/
522     bool setEnv(string name, string value) {
523         auto rc = ssh_channel_request_env(this._channel, toStrZ(name), toStrZ(value));
524         if (rc == SSH_AGAIN) {
525             return false;
526         }
527         if (rc != SSH_OK) {
528             throw new SSHException(this._parent._session);
529         }
530         return true;
531     }
532 
533     void changePtySize(int width, int height) {
534         auto rc = ssh_channel_change_pty_size(this._channel, width, height);
535         if (rc != SSH_OK) {
536             throw new SSHException(this._parent._session);
537         }
538     }
539 
540     SSHChannel acceptX11(int timeoutMs) {
541         auto result = ssh_channel_accept_x11(this._channel, timeoutMs);
542         if (result is null) {
543             return null;
544         }
545         return new SSHChannel(this._parent, result);
546     }
547 
548     /**
549      * return false if in nonblocking mode and call has to be done again.
550      * */
551     bool openAuthAgent() {
552         auto rc = ssh_channel_open_auth_agent(this._channel);
553         if (rc == SSH_AGAIN) {
554             return false;
555         }
556         if (rc != SSH_OK) {
557             throw new SSHException(this._parent._session);
558         }
559         return true;
560     }
561 
562     /**
563      * return false if in nonblocking mode and call has to be done again.
564      * */
565     bool openForward(string remoteHost, ushort remotePort, string srcHost, 
566             ushort localPort) {
567         assert(remoteHost !is null);
568         auto rc = ssh_channel_open_forward(this._channel, toStrZ(remoteHost), remotePort,
569             toStrZ(srcHost), localPort);
570         if (rc == SSH_AGAIN) {
571             return false;
572         }
573         if (rc != SSH_OK) {
574             throw new SSHException(this._parent._session);
575         }
576         return true;
577     }
578 
579     /**
580      * return false if in nonblocking mode and call has to be done again.
581      * */
582     bool openReverseForward(string remoteHost, ushort remotePort, string srcHost,
583             ushort localPort) {
584         assert(remoteHost !is null);
585         auto rc = ssh_channel_open_reverse_forward(this._channel, toStrZ(remoteHost), remotePort,
586             toStrZ(srcHost), localPort);
587         if (rc == SSH_AGAIN) {
588             return false;
589         }
590         if (rc != SSH_OK) {
591             throw new SSHException(this._parent._session);
592         }
593         return true;
594     }
595 
596     /**
597      * returns false if in nonblocking mode and call has to be done again.
598      **/
599     bool openX11(string origAddr, ushort origPort) {
600         auto rc = ssh_channel_open_x11(this._channel, toStrZ(origAddr), origPort);
601         if (rc == SSH_AGAIN) {
602             return false;
603         }
604         if (rc != SSH_OK) {
605             throw new SSHException(this._parent._session);
606         }
607         return true;
608     }
609 
610 
611     void close() {
612         auto rc = ssh_channel_close(this._channel);
613         if (rc != SSH_OK) {
614             throw new SSHException(this._parent._session);
615         }
616     }
617 
618     override void dispose() {
619         this._dispose(false);
620     }
621 
622     /**
623      * return false if the select(2) syscall was interrupted, then relaunch the function.
624      * */
625     static bool select(SSHChannelSet readChans, SSHChannelSet writeChans, 
626             SSHChannelSet exceptChans, Duration timeout) {
627 
628         timeval timeoutVal;
629         timeout.split!("seconds", "usecs")(timeoutVal.tv_sec, timeoutVal.tv_usec);
630 
631         ssh_channel[] forRead = null;
632         ssh_channel[] forWrite = null;
633         ssh_channel[] forExcept = null;
634        
635         if (readChans !is null && readChans._channels.length > 0) {
636             forRead = new ssh_channel[readChans._channels.length + 1];
637             for (auto i = 0; i < readChans._channels.length; i++) {
638                 forRead[i] = readChans._channels[i]._channel;
639             }
640             forRead[readChans._channels.length] = null;
641         }
642 
643         if (writeChans !is null && writeChans._channels.length > 0) {
644             forWrite = new ssh_channel[writeChans._channels.length + 1];
645             for (auto i = 0; i < writeChans._channels.length; i++) {
646                 forWrite[i] = writeChans._channels[i]._channel;
647             }
648             forWrite[writeChans._channels.length] = null;
649         }
650 
651         if (exceptChans !is null && exceptChans._channels.length > 0) {
652             forExcept = new ssh_channel[exceptChans._channels.length + 1];
653             for (auto i = 0; i < exceptChans._channels.length; i++) {
654                 forExcept[i] = exceptChans._channels[i]._channel;
655             }
656             forExcept[exceptChans._channels.length] = null;
657         }
658 
659         ssh_channel* forReadPtr = forRead !is null ? forRead.ptr : null;
660         ssh_channel* forWritePtr = forWrite !is null ? forWrite.ptr : null;
661         ssh_channel* forExceptPtr = forExcept !is null ? forExcept.ptr : null;
662 
663         auto rc = ssh_channel_select(forReadPtr, forWritePtr, forExceptPtr, &timeoutVal);
664 
665         if (rc == ssh_error_types_e.SSH_EINTR || rc == SSH_AGAIN) {
666             return false;
667         }
668         checkForRCError(rc, rc);
669 
670         size_t i = 0;
671         if (forRead !is null) {
672             while (forRead[i] !is null) {
673                 readChans._settedChannels ~= forRead[i];
674                 i += 1;
675             }
676         }
677 
678         if (forWrite !is null) {
679             i = 0;
680             while (forWrite[i] !is null) {
681                 writeChans._settedChannels ~= forWrite[i];
682                 i += 1;
683             }
684         }
685 
686         if (forExcept !is null) {
687             i = 0;
688             while (forExcept[i] !is null) {
689                 exceptChans._settedChannels ~= forExcept[i];
690                 i += 1;
691             }
692         }
693 
694         return true;
695     }
696 
697     ~this() {
698         this._dispose(true);
699     }
700 
701     package {
702         this(SSHSession parent, ssh_channel channel) {
703             this._parent = parent;
704             this._channel = channel;
705             parent.registerChannel(this);
706 
707             ssh_callbacks_init(this._callbacks);
708             this._callbacks.userdata = cast(void*) this;
709         }
710 
711         ssh_channel _channel;
712     }
713 
714     private {
715         SSHSession _parent;     // Need to GC not deleted session before any user code stopped using of this object
716 
717         ssh_channel_callbacks_struct _callbacks;
718 
719         OnEOFCallback _onEOFCallback;
720         OnCloseCallback _onCloseCallback;
721         OnSignalCallback _onSignalCallback;
722         OnExitStatusCallback _onExitStatusCallback;
723         OnExitSignalCallback _onExitSignalCallback;
724         OnAuthAgentRequestCallback _onAuthAgentRequestCallback;
725         OnX11RequestCallback _onX11RequestCallback;
726         OnDataCallback _onDataCallback;
727         OnPtyRequestCallback _onPtyRequestCallback;
728         OnShellRequestCallback _onShellRequestCallback;
729         OnPtyWindowChangeRequestCallback _onPtyWindowChangeRequestCallback;
730         OnExecRequestCallback _onExecRequestCallback;
731         OnEnvRequestCallback _onEnvRequestCallback;
732         OnSubsystemRequestCallback _onSubsystemRequestCallback;
733 
734         void _dispose(bool fromDtor) {
735             if (this._channel !is null) {
736                 ssh_channel_free(this._channel);
737 
738                 if (!fromDtor) {
739                     this._parent.freeChannel(this);
740                 }
741 
742                 this._channel = null;
743                 this._parent = null;
744             }
745         }
746     }
747 }
748 
749 private {
750     extern(C) int nativeOnData(ssh_session session, ssh_channel channel, void *data, uint len, 
751             int is_stderr, void* userdata) {
752         auto channelObj = cast(SSHChannel) userdata;
753 
754         if (channelObj._onDataCallback is null) {
755             return SSH_ERROR;
756         }
757 
758         try {
759             return channelObj._onDataCallback(channelObj, data[0 .. len], 
760                 is_stderr == 0 ? false : true);
761         } catch (Exception) {
762             return SSH_ERROR;
763         }
764     }
765 
766     extern(C) void nativeOnEOFCallback(ssh_session session, ssh_channel channel, void* userdata) {
767         auto channelObj = cast(SSHChannel) userdata;
768 
769         if (channelObj is null || channelObj._onEOFCallback is null) {
770             return;
771         }
772 
773         channelObj._onEOFCallback(channelObj);
774     }
775 
776     extern(C) void nativeOnCloseCallback(ssh_session session, ssh_channel channel, void* userdata) {
777         auto channelObj = cast(SSHChannel) userdata;
778 
779         if (channelObj is null || channelObj._onCloseCallback is null) {
780             return;
781         }
782 
783         channelObj._onCloseCallback(channelObj);
784     }
785 
786     extern(C) void nativeOnSignalCallback(ssh_session session, ssh_channel channel, const char* signal,
787             void* userdata) {
788         auto channelObj = cast(SSHChannel) userdata;
789 
790         if (channelObj is null || channelObj._onSignalCallback is null) {
791             return;
792         }
793 
794         channelObj._onSignalCallback(channelObj, fromStrZ(signal));
795     }
796 
797     extern(C) void nativeOnExitStatusCallback(ssh_session session, ssh_channel channel, int exit_status,
798             void* userdata) {
799         auto channelObj = cast(SSHChannel) userdata;
800 
801         if (channelObj is null || channelObj._onExitStatusCallback is null) {
802             return;
803         }
804         
805         channelObj._onExitStatusCallback(channelObj, exit_status);
806     }
807 
808     extern(C) void nativeOnExitSignalCallback(ssh_session session, ssh_channel channel, const char* signal,
809             int core, const char* errmsg, const char* lang, void* userdata) {
810         auto channelObj = cast(SSHChannel) userdata;
811 
812         if (channelObj is null || channelObj._onExitSignalCallback is null) {
813             return;
814         }
815 
816         channelObj._onExitSignalCallback(channelObj, fromStrZ(signal), core == 0 ? false : true,
817             fromStrZ(errmsg), fromStrZ(lang));
818     }
819 
820     extern(C) int nativeOnPtyRequestCallback(ssh_session session, ssh_channel channel, 
821             const char* term, int width, int height, int pxwidth, int pxheight, void *userdata) {
822         auto channelObj = cast(SSHChannel) userdata;
823 
824         if (channelObj._onPtyRequestCallback is null) {
825             return -1;
826         }
827 
828         try {
829             auto result = channelObj._onPtyRequestCallback(channelObj, fromStrZ(term), width,
830                 height, pxwidth, pxheight);
831             return result ? 0 : -1;
832         } catch(Exception) {
833             return -1;
834         }
835     }
836 
837     extern(C) int nativeOnShellRequestCallback(ssh_session session, ssh_channel channel,
838             void *userdata) {
839         auto channelObj = cast(SSHChannel) userdata;
840 
841         if (channelObj._onShellRequestCallback is null) {
842             return 1;
843         }
844 
845         try {
846             auto result = channelObj._onShellRequestCallback(channelObj);
847             return result ? 0 : 1;
848         } catch(Exception) {
849             return 1;
850         }
851     }
852 
853     extern(C) void nativeOnAuthAgentRequestCallback(ssh_session session, ssh_channel channel, 
854             void* userdata) {
855         auto channelObj = cast(SSHChannel) userdata;
856 
857         if (channelObj is null || channelObj._onAuthAgentRequestCallback is null) {
858             return;
859         }
860 
861         channelObj._onAuthAgentRequestCallback(channelObj);
862     }
863 
864     extern(C) void nativeOnX11tRequestCallback(ssh_session session, ssh_channel channel, 
865             int single_connection, const char *auth_protocol, const char *auth_cookie, 
866             uint screen_number, void* userdata) {
867         auto channelObj = cast(SSHChannel) userdata;
868 
869         if (channelObj is null || channelObj._onX11RequestCallback is null) {
870             return;
871         }
872 
873         channelObj._onX11RequestCallback(channelObj, single_connection == 0 ? false : true,
874                 fromStrZ(auth_protocol), fromStrZ(auth_cookie), screen_number);
875     }
876 
877     extern(C) int nativeOnPtyWindowChangeRequestCallback(ssh_session session, ssh_channel channel, 
878             int width, int height, int pxwidth, int pxheight, void *userdata) {
879         auto channelObj = cast(SSHChannel) userdata;
880 
881         if (channelObj._onPtyWindowChangeRequestCallback is null) {
882             return -1;
883         }
884 
885         try {
886             auto result = channelObj._onPtyWindowChangeRequestCallback(channelObj, width, height, 
887                 pxwidth, pxheight);
888             return result ? 0 : -1;
889         } catch(Exception) {
890             return -1;
891         }
892     }
893 
894     extern(C) int nativeOnExecRequestCallback(ssh_session session, ssh_channel channel,
895             const char* command, void* userdata) {
896         auto channelObj = cast(SSHChannel) userdata;
897 
898         if (channelObj._onExecRequestCallback is null) {
899             return 1;
900         }
901 
902         try {
903             auto result = channelObj._onExecRequestCallback(channelObj, fromStrZ(command));
904             return result ? 0 : 1;
905         } catch(Exception) {
906             return -1;
907         }
908     }
909 
910     extern(C) int nativeOnEnvRequestCallback(ssh_session session, ssh_channel channel,
911             const char* env_name, const char* env_value, void* userdata) {
912         auto channelObj = cast(SSHChannel) userdata;
913 
914         if (channelObj._onEnvRequestCallback is null) {
915             return 1;
916         }
917 
918         try {
919             auto result = channelObj._onEnvRequestCallback(channelObj, fromStrZ(env_name),
920                 fromStrZ(env_value));
921             return result ? 0 : 1;
922         } catch (Exception) {
923             return 1;
924         }
925     }
926 
927     extern(C) int nativeOnSubsystemRequestCallback(ssh_session session, ssh_channel channel,
928             const char* subsystem, void* userdata) {
929         auto channelObj = cast(SSHChannel) userdata;
930         
931         if (channelObj._onSubsystemRequestCallback is null) {
932             return 1;
933         }
934         
935         try {
936             auto result = channelObj._onSubsystemRequestCallback(channelObj, fromStrZ(subsystem));
937             return result ? 0 : 1;
938         } catch (Exception) {
939             return 1;
940         }
941     }
942 }