1 /*
2 * This file is part of the SSH Library
3 *
4 * Copyright (c) 2010 by Aris Adamantiadis
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #ifndef LIBSSHPP_HPP_
22 #define LIBSSHPP_HPP_
23
24 /**
25 * @defgroup ssh_cpp The libssh C++ wrapper
26 *
27 * The C++ bindings for libssh are completely embedded in a single .hpp file, and
28 * this for two reasons:
29 * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version
30 * as much as possible binary compatible between releases, while this would be hard for
31 * C++. If you compile your program with these headers, you will only link to the C version
32 * of libssh which will be kept ABI compatible. No need to recompile your C++ program
33 * each time a new binary-compatible version of libssh is out
34 * - Most of the functions in this file are really short and are probably worth the "inline"
35 * linking mode, which the compiler can decide to do in some case. There would be nearly no
36 * performance penalty of using the wrapper rather than native calls.
37 *
38 * Please visit the documentation of ssh::Session and ssh::Channel
39 * @see ssh::Session
40 * @see ssh::Channel
41 *
42 * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS:
43 * @code
44 * #define SSH_NO_CPP_EXCEPTIONS
45 * #include <libssh/libsshpp.hpp>
46 * @endcode
47 * All functions will then return SSH_ERROR in case of error.
48 * @{
49 */
50
51 /* do not use deprecated functions */
52 #define LIBSSH_LEGACY_0_4
53
54 #include <libssh/libssh.h>
55 #include <libssh/server.h>
56 #include <stdlib.h>
57 #include <stdarg.h>
58 #include <stdio.h>
59 #include <string>
60
61 namespace ssh {
62
63 class Channel;
64 /** Some people do not like C++ exceptions. With this define, we give
65 * the choice to use or not exceptions.
66 * @brief if defined, disable C++ exceptions for libssh c++ wrapper
67 */
68 #ifndef SSH_NO_CPP_EXCEPTIONS
69
70 /** @brief This class describes a SSH Exception object. This object can be thrown
71 * by several SSH functions that interact with the network, and may fail because of
72 * socket, protocol or memory errors.
73 */
74 class SshException{
75 public:
SshException(ssh_session csession)76 SshException(ssh_session csession){
77 code=ssh_get_error_code(csession);
78 description=std::string(ssh_get_error(csession));
79 }
SshException(const SshException & e)80 SshException(const SshException &e){
81 code=e.code;
82 description=e.description;
83 }
84 /** @brief returns the Error code
85 * @returns SSH_FATAL Fatal error happened (not recoverable)
86 * @returns SSH_REQUEST_DENIED Request was denied by remote host
87 * @see ssh_get_error_code
88 */
getCode()89 int getCode(){
90 return code;
91 }
92 /** @brief returns the error message of the last exception
93 * @returns pointer to a c string containing the description of error
94 * @see ssh_get_error
95 */
getError()96 std::string getError(){
97 return description;
98 }
99 private:
100 int code;
101 std::string description;
102 };
103
104 /** @internal
105 * @brief Macro to throw exception if there was an error
106 */
107 #define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession())
108 #define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession)
109 #define void_throwable void
110 #define return_throwable return
111
112 #else
113
114 /* No exception at all. All functions will return an error code instead
115 * of an exception
116 */
117 #define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR
118 #define ssh_throw_null(CSession,x) if((x)==NULL) return NULL
119 #define void_throwable int
120 #define return_throwable return SSH_OK
121 #endif
122
123 /**
124 * The ssh::Session class contains the state of a SSH connection.
125 */
126 class Session {
127 friend class Channel;
128 public:
Session()129 Session(){
130 c_session=ssh_new();
131 }
~Session()132 ~Session(){
133 ssh_free(c_session);
134 c_session=NULL;
135 }
136 /** @brief sets an SSH session options
137 * @param type Type of option
138 * @param option cstring containing the value of option
139 * @throws SshException on error
140 * @see ssh_options_set
141 */
setOption(enum ssh_options_e type,const char * option)142 void_throwable setOption(enum ssh_options_e type, const char *option){
143 ssh_throw(ssh_options_set(c_session,type,option));
144 return_throwable;
145 }
146 /** @brief sets an SSH session options
147 * @param type Type of option
148 * @param option long integer containing the value of option
149 * @throws SshException on error
150 * @see ssh_options_set
151 */
setOption(enum ssh_options_e type,long int option)152 void_throwable setOption(enum ssh_options_e type, long int option){
153 ssh_throw(ssh_options_set(c_session,type,&option));
154 return_throwable;
155 }
156 /** @brief sets an SSH session options
157 * @param type Type of option
158 * @param option void pointer containing the value of option
159 * @throws SshException on error
160 * @see ssh_options_set
161 */
setOption(enum ssh_options_e type,void * option)162 void_throwable setOption(enum ssh_options_e type, void *option){
163 ssh_throw(ssh_options_set(c_session,type,option));
164 return_throwable;
165 }
166 /** @brief connects to the remote host
167 * @throws SshException on error
168 * @see ssh_connect
169 */
connect()170 void_throwable connect(){
171 int ret=ssh_connect(c_session);
172 ssh_throw(ret);
173 return_throwable;
174 }
175 /** @brief Authenticates automatically using public key
176 * @throws SshException on error
177 * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
178 * @see ssh_userauth_autopubkey
179 */
userauthPublickeyAuto(void)180 int userauthPublickeyAuto(void){
181 int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL);
182 ssh_throw(ret);
183 return ret;
184 }
185 /** @brief Authenticates using the "none" method. Prefer using autopubkey if
186 * possible.
187 * @throws SshException on error
188 * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
189 * @see ssh_userauth_none
190 * @see Session::userauthAutoPubkey
191 */
userauthNone()192 int userauthNone(){
193 int ret=ssh_userauth_none(c_session,NULL);
194 ssh_throw(ret);
195 return ret;
196 }
197
198 /**
199 * @brief Authenticate through the "keyboard-interactive" method.
200 *
201 * @param[in] username The username to authenticate. You can specify NULL if
202 * ssh_option_set_username() has been used. You cannot
203 * try two different logins in a row.
204 *
205 * @param[in] submethods Undocumented. Set it to NULL.
206 *
207 * @throws SshException on error
208 *
209 * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED,
210 * SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN
211 *
212 * @see ssh_userauth_kbdint
213 */
userauthKbdint(const char * username,const char * submethods)214 int userauthKbdint(const char* username, const char* submethods){
215 int ret = ssh_userauth_kbdint(c_session, username, submethods);
216 ssh_throw(ret);
217 return ret;
218 }
219
220 /** @brief Get the number of prompts (questions) the server has given.
221 * @returns The number of prompts.
222 * @see ssh_userauth_kbdint_getnprompts
223 */
userauthKbdintGetNPrompts()224 int userauthKbdintGetNPrompts(){
225 return ssh_userauth_kbdint_getnprompts(c_session);
226 }
227
228 /**
229 * @brief Set the answer for a question from a message block.
230 *
231 * @param[in] index The index number of the prompt.
232 * @param[in] answer The answer to give to the server. The answer MUST be
233 * encoded UTF-8. It is up to the server how to interpret
234 * the value and validate it. However, if you read the
235 * answer in some other encoding, you MUST convert it to
236 * UTF-8.
237 *
238 * @throws SshException on error
239 *
240 * @returns 0 on success, < 0 on error
241 *
242 * @see ssh_userauth_kbdint_setanswer
243 */
userauthKbdintSetAnswer(unsigned int index,const char * answer)244 int userauthKbdintSetAnswer(unsigned int index, const char *answer)
245 {
246 int ret = ssh_userauth_kbdint_setanswer(c_session, index, answer);
247 ssh_throw(ret);
248 return ret;
249 }
250
251
252
253 /** @brief Authenticates using the password method.
254 * @param[in] password password to use for authentication
255 * @throws SshException on error
256 * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
257 * @see ssh_userauth_password
258 */
userauthPassword(const char * password)259 int userauthPassword(const char *password){
260 int ret=ssh_userauth_password(c_session,NULL,password);
261 ssh_throw(ret);
262 return ret;
263 }
264 /** @brief Try to authenticate using the publickey method.
265 * @param[in] pubkey public key to use for authentication
266 * @throws SshException on error
267 * @returns SSH_AUTH_SUCCESS if the pubkey is accepted,
268 * @returns SSH_AUTH_DENIED if the pubkey is denied
269 * @see ssh_userauth_try_pubkey
270 */
userauthTryPublickey(ssh_key pubkey)271 int userauthTryPublickey(ssh_key pubkey){
272 int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey);
273 ssh_throw(ret);
274 return ret;
275 }
276 /** @brief Authenticates using the publickey method.
277 * @param[in] privkey private key to use for authentication
278 * @throws SshException on error
279 * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
280 * @see ssh_userauth_pubkey
281 */
userauthPublickey(ssh_key privkey)282 int userauthPublickey(ssh_key privkey){
283 int ret=ssh_userauth_publickey(c_session, NULL, privkey);
284 ssh_throw(ret);
285 return ret;
286 }
287
288 /** @brief Returns the available authentication methods from the server
289 * @throws SshException on error
290 * @returns Bitfield of available methods.
291 * @see ssh_userauth_list
292 */
getAuthList()293 int getAuthList(){
294 int ret=ssh_userauth_list(c_session, NULL);
295 ssh_throw(ret);
296 return ret;
297 }
298 /** @brief Disconnects from the SSH server and closes connection
299 * @see ssh_disconnect
300 */
disconnect()301 void disconnect(){
302 ssh_disconnect(c_session);
303 }
304 /** @brief Returns the disconnect message from the server, if any
305 * @returns pointer to the message, or NULL. Do not attempt to free
306 * the pointer.
307 */
getDisconnectMessage()308 const char *getDisconnectMessage(){
309 const char *msg=ssh_get_disconnect_message(c_session);
310 return msg;
311 }
312 /** @internal
313 * @brief gets error message
314 */
getError()315 const char *getError(){
316 return ssh_get_error(c_session);
317 }
318 /** @internal
319 * @brief returns error code
320 */
getErrorCode()321 int getErrorCode(){
322 return ssh_get_error_code(c_session);
323 }
324 /** @brief returns the file descriptor used for the communication
325 * @returns the file descriptor
326 * @warning if a proxycommand is used, this function will only return
327 * one of the two file descriptors being used
328 * @see ssh_get_fd
329 */
getSocket()330 socket_t getSocket(){
331 return ssh_get_fd(c_session);
332 }
333 /** @brief gets the Issue banner from the ssh server
334 * @returns the issue banner. This is generally a MOTD from server
335 * @see ssh_get_issue_banner
336 */
getIssueBanner()337 std::string getIssueBanner(){
338 char *banner = ssh_get_issue_banner(c_session);
339 std::string ret = "";
340 if (banner != NULL) {
341 ret = std::string(banner);
342 ::free(banner);
343 }
344 return ret;
345 }
346 /** @brief returns the OpenSSH version (server) if possible
347 * @returns openssh version code
348 * @see ssh_get_openssh_version
349 */
getOpensshVersion()350 int getOpensshVersion(){
351 return ssh_get_openssh_version(c_session);
352 }
353 /** @brief returns the version of the SSH protocol being used
354 * @returns the SSH protocol version
355 * @see ssh_get_version
356 */
getVersion()357 int getVersion(){
358 return ssh_get_version(c_session);
359 }
360 /** @brief verifies that the server is known
361 * @throws SshException on error
362 * @returns Integer value depending on the knowledge of the
363 * server key
364 * @see ssh_session_update_known_hosts
365 */
isServerKnown()366 int isServerKnown(){
367 int state = ssh_session_is_known_server(c_session);
368 ssh_throw(state);
369 return state;
370 }
log(int priority,const char * format,...)371 void log(int priority, const char *format, ...){
372 char buffer[1024];
373 va_list va;
374
375 va_start(va, format);
376 vsnprintf(buffer, sizeof(buffer), format, va);
377 va_end(va);
378 _ssh_log(priority, "libsshpp", "%s", buffer);
379 }
380
381 /** @brief copies options from a session to another
382 * @throws SshException on error
383 * @see ssh_options_copy
384 */
optionsCopy(const Session & source)385 void_throwable optionsCopy(const Session &source){
386 ssh_throw(ssh_options_copy(source.c_session,&c_session));
387 return_throwable;
388 }
389 /** @brief parses a configuration file for options
390 * @throws SshException on error
391 * @param[in] file configuration file name
392 * @see ssh_options_parse_config
393 */
optionsParseConfig(const char * file)394 void_throwable optionsParseConfig(const char *file){
395 ssh_throw(ssh_options_parse_config(c_session,file));
396 return_throwable;
397 }
398 /** @brief silently disconnect from remote host
399 * @see ssh_silent_disconnect
400 */
silentDisconnect()401 void silentDisconnect(){
402 ssh_silent_disconnect(c_session);
403 }
404 /** @brief Writes the known host file with current
405 * host key
406 * @throws SshException on error
407 * @see ssh_write_knownhost
408 */
writeKnownhost()409 int writeKnownhost(){
410 int ret = ssh_session_update_known_hosts(c_session);
411 ssh_throw(ret);
412 return ret;
413 }
414
415 /** @brief accept an incoming forward connection
416 * @param[in] timeout_ms timeout for waiting, in ms
417 * @returns new Channel pointer on the forward connection
418 * @returns NULL in case of error
419 * @warning you have to delete this pointer after use
420 * @see ssh_channel_forward_accept
421 * @see Session::listenForward
422 */
423 inline Channel *acceptForward(int timeout_ms);
424 /* implemented outside the class due Channel references */
425
cancelForward(const char * address,int port)426 void_throwable cancelForward(const char *address, int port){
427 int err=ssh_channel_cancel_forward(c_session, address, port);
428 ssh_throw(err);
429 return_throwable;
430 }
431
listenForward(const char * address,int port,int & boundport)432 void_throwable listenForward(const char *address, int port,
433 int &boundport){
434 int err=ssh_channel_listen_forward(c_session, address, port, &boundport);
435 ssh_throw(err);
436 return_throwable;
437 }
438
getCSession()439 ssh_session getCSession(){
440 return c_session;
441 }
442
443 protected:
444 ssh_session c_session;
445
446 private:
447 /* No copy constructor, no = operator */
448 Session(const Session &);
449 Session& operator=(const Session &);
450 };
451
452 /** @brief the ssh::Channel class describes the state of an SSH
453 * channel.
454 * @see ssh_channel
455 */
456 class Channel {
457 friend class Session;
458 public:
Channel(Session & ssh_session)459 Channel(Session &ssh_session){
460 channel = ssh_channel_new(ssh_session.getCSession());
461 this->session = &ssh_session;
462 }
~Channel()463 ~Channel(){
464 ssh_channel_free(channel);
465 channel=NULL;
466 }
467
468 /** @brief accept an incoming X11 connection
469 * @param[in] timeout_ms timeout for waiting, in ms
470 * @returns new Channel pointer on the X11 connection
471 * @returns NULL in case of error
472 * @warning you have to delete this pointer after use
473 * @see ssh_channel_accept_x11
474 * @see Channel::requestX11
475 */
acceptX11(int timeout_ms)476 Channel *acceptX11(int timeout_ms){
477 ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms);
478 ssh_throw_null(getCSession(),x11chan);
479 Channel *newchan = new Channel(getSession(),x11chan);
480 return newchan;
481 }
482 /** @brief change the size of a pseudoterminal
483 * @param[in] cols number of columns
484 * @param[in] rows number of rows
485 * @throws SshException on error
486 * @see ssh_channel_change_pty_size
487 */
changePtySize(int cols,int rows)488 void_throwable changePtySize(int cols, int rows){
489 int err=ssh_channel_change_pty_size(channel,cols,rows);
490 ssh_throw(err);
491 return_throwable;
492 }
493
494 /** @brief closes a channel
495 * @throws SshException on error
496 * @see ssh_channel_close
497 */
close()498 void_throwable close(){
499 ssh_throw(ssh_channel_close(channel));
500 return_throwable;
501 }
502
getExitStatus()503 int getExitStatus(){
504 return ssh_channel_get_exit_status(channel);
505 }
getSession()506 Session &getSession(){
507 return *session;
508 }
509 /** @brief returns true if channel is in closed state
510 * @see ssh_channel_is_closed
511 */
isClosed()512 bool isClosed(){
513 return ssh_channel_is_closed(channel) != 0;
514 }
515 /** @brief returns true if channel is in EOF state
516 * @see ssh_channel_is_eof
517 */
isEof()518 bool isEof(){
519 return ssh_channel_is_eof(channel) != 0;
520 }
521 /** @brief returns true if channel is in open state
522 * @see ssh_channel_is_open
523 */
isOpen()524 bool isOpen(){
525 return ssh_channel_is_open(channel) != 0;
526 }
openForward(const char * remotehost,int remoteport,const char * sourcehost=NULL,int localport=0)527 int openForward(const char *remotehost, int remoteport,
528 const char *sourcehost=NULL, int localport=0){
529 int err=ssh_channel_open_forward(channel,remotehost,remoteport,
530 sourcehost, localport);
531 ssh_throw(err);
532 return err;
533 }
534 /* TODO: completely remove this ? */
openSession()535 void_throwable openSession(){
536 int err=ssh_channel_open_session(channel);
537 ssh_throw(err);
538 return_throwable;
539 }
poll(bool is_stderr=false)540 int poll(bool is_stderr=false){
541 int err=ssh_channel_poll(channel,is_stderr);
542 ssh_throw(err);
543 return err;
544 }
read(void * dest,size_t count)545 int read(void *dest, size_t count){
546 int err;
547 /* handle int overflow */
548 if(count > 0x7fffffff)
549 count = 0x7fffffff;
550 err=ssh_channel_read_timeout(channel,dest,count,false,-1);
551 ssh_throw(err);
552 return err;
553 }
read(void * dest,size_t count,int timeout)554 int read(void *dest, size_t count, int timeout){
555 int err;
556 /* handle int overflow */
557 if(count > 0x7fffffff)
558 count = 0x7fffffff;
559 err=ssh_channel_read_timeout(channel,dest,count,false,timeout);
560 ssh_throw(err);
561 return err;
562 }
read(void * dest,size_t count,bool is_stderr=false,int timeout=-1)563 int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){
564 int err;
565 /* handle int overflow */
566 if(count > 0x7fffffff)
567 count = 0x7fffffff;
568 err=ssh_channel_read_timeout(channel,dest,count,is_stderr,timeout);
569 ssh_throw(err);
570 return err;
571 }
readNonblocking(void * dest,size_t count,bool is_stderr=false)572 int readNonblocking(void *dest, size_t count, bool is_stderr=false){
573 int err;
574 /* handle int overflow */
575 if(count > 0x7fffffff)
576 count = 0x7fffffff;
577 err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr);
578 ssh_throw(err);
579 return err;
580 }
requestEnv(const char * name,const char * value)581 void_throwable requestEnv(const char *name, const char *value){
582 int err=ssh_channel_request_env(channel,name,value);
583 ssh_throw(err);
584 return_throwable;
585 }
586
requestExec(const char * cmd)587 void_throwable requestExec(const char *cmd){
588 int err=ssh_channel_request_exec(channel,cmd);
589 ssh_throw(err);
590 return_throwable;
591 }
requestPty(const char * term=NULL,int cols=0,int rows=0)592 void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){
593 int err;
594 if(term != NULL && cols != 0 && rows != 0)
595 err=ssh_channel_request_pty_size(channel,term,cols,rows);
596 else
597 err=ssh_channel_request_pty(channel);
598 ssh_throw(err);
599 return_throwable;
600 }
601
requestShell()602 void_throwable requestShell(){
603 int err=ssh_channel_request_shell(channel);
604 ssh_throw(err);
605 return_throwable;
606 }
requestSendSignal(const char * signum)607 void_throwable requestSendSignal(const char *signum){
608 int err=ssh_channel_request_send_signal(channel, signum);
609 ssh_throw(err);
610 return_throwable;
611 }
requestSubsystem(const char * subsystem)612 void_throwable requestSubsystem(const char *subsystem){
613 int err=ssh_channel_request_subsystem(channel,subsystem);
614 ssh_throw(err);
615 return_throwable;
616 }
requestX11(bool single_connection,const char * protocol,const char * cookie,int screen_number)617 int requestX11(bool single_connection,
618 const char *protocol, const char *cookie, int screen_number){
619 int err=ssh_channel_request_x11(channel,single_connection,
620 protocol, cookie, screen_number);
621 ssh_throw(err);
622 return err;
623 }
sendEof()624 void_throwable sendEof(){
625 int err=ssh_channel_send_eof(channel);
626 ssh_throw(err);
627 return_throwable;
628 }
629 /** @brief Writes on a channel
630 * @param data data to write.
631 * @param len number of bytes to write.
632 * @param is_stderr write should be done on the stderr channel (server only)
633 * @returns number of bytes written
634 * @throws SshException in case of error
635 * @see channel_write
636 * @see channel_write_stderr
637 */
write(const void * data,size_t len,bool is_stderr=false)638 int write(const void *data, size_t len, bool is_stderr=false){
639 int ret;
640 if(is_stderr){
641 ret=ssh_channel_write_stderr(channel,data,len);
642 } else {
643 ret=ssh_channel_write(channel,data,len);
644 }
645 ssh_throw(ret);
646 return ret;
647 }
648
getCSession()649 ssh_session getCSession(){
650 return session->getCSession();
651 }
652
getCChannel()653 ssh_channel getCChannel() {
654 return channel;
655 }
656
657 protected:
658 Session *session;
659 ssh_channel channel;
660
661 private:
Channel(Session & ssh_session,ssh_channel c_channel)662 Channel (Session &ssh_session, ssh_channel c_channel){
663 this->channel=c_channel;
664 this->session = &ssh_session;
665 }
666 /* No copy and no = operator */
667 Channel(const Channel &);
668 Channel &operator=(const Channel &);
669 };
670
671
acceptForward(int timeout_ms)672 inline Channel *Session::acceptForward(int timeout_ms){
673 ssh_channel forward =
674 ssh_channel_accept_forward(c_session, timeout_ms, NULL);
675 ssh_throw_null(c_session,forward);
676 Channel *newchan = new Channel(*this,forward);
677 return newchan;
678 }
679
680 } // namespace ssh
681
682 /** @} */
683 #endif /* LIBSSHPP_HPP_ */
684