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