如何使用ssh协议开发文件应用

使用ssh客户端,登录到ssh服务器,或者使用scp命令,从ssh服务器上传或者下载文件,非常方便。

但是,如何使用ssh协议,开发文件的处理,集成到我们的应用中呢?其实非常简单,libssh2都给我们包装好了。

环境部署

如何使用Redhat族系统,比如fedora,可以使用

dnf install libssh2-devel

来安装开发库。如果是Debian族的系统,则可以使用

apt install libssh2-1-dev

来安装。

安装好以后,就可以使用

pkg-config --cflags

来获取C/C++语言的头文件包含参数。

使用

pkg-config --libs

来获取C/C++的动态连接库参数。

API以及通用流程

ssh协议中,使用文件相关的api以及通用的api,都在libssh2.h中。而涉及到目录相关的api,一般都在libssh2_sftp.h中。

在使用ssh2协议开始之前,需要进行全局的初始化,即调用:

int libssh2_init(int flags);

flags如果不设置为0,其实只有一个可用的值:LIBSSH2_INIT_NO_CRYPTO。

不再使用ssh2协议之后,可以调用:

void libssh2_exit(void);

来释放相应资源。

  • 注:为简化理解,以下函数说明中,均使用了库文件中的没有_ex的宏。其实带_ex的函数,只是把一些字符串长度传递到了函数中,与直接使用宏定义的版本区别不大。

一般来说,进行一个ssh协议的文件或者目录操作,需要这样几步:

  1. 连接
  2. 认证
  3. 打开操作句柄
  4. 操作
  5. 关闭句柄

连接

使用ssh2建立连接的过程,与使用SSL建立连接的过程很类似,都需要我们手动建立一个TCP连接,之后再把一个ssh2的Session关联到TCP连接上去。

即,先创建一个socket,然后调用connect,然后调用:

LIBSSH2_SESSION * libssh2_session_init ();

创建一个session,再使用

int libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock);

建立ssh2协议的握手。

认证

握手成功以后,先获取ssh服务器支持的认证方式,然后选择一个,进行认证。

获取认证方式的函数为:

char *libssh2_userauth_list(LIBSSH2_SESSION *session,
                                        const char *username,
                                        unsigned int username_len);

认证的函数有几个,分别对应几种认证方式,分别为:

// 密码
int  
libssh2_userauth_password(LIBSSH2_SESSION *session,  
                            const char *username,    
                            const char *password,  
                            LIBSSH2_PASSWD_CHANGEREQ_FUNC  
                                ((*passwd_change_cb)));


// 交互式密码
int  
libssh2_userauth_keyboard_interactive(LIBSSH2_SESSION* session,  
                                        const char *username, 
                                        LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC  
                                            ((*response_callback)));


// 证书认证
int  
libssh2_userauth_publickey(LIBSSH2_SESSION *session,  
                          const char *username,  
                          const unsigned char *pubkeydata,  
                          size_t pubkeydata_len,  
                          LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC  
                              ((*sign_callback)),  
                          void **abstract);
int  
libssh2_userauth_publickey_fromfile(LIBSSH2_SESSION *session,  
                                      const char *username,  
                                      const char *publickey,  
                                      const char *privatekey,  
                                      const char *passphrase);

操作

连接成功以后,就可以调用文件或者目录相关的API了。下面在具体的例子中说明。

连接示例代码:

int
ssh2_connect (const char *username, const char *pass, const char *sshkey_pub, const char *sshkey_pri)
{
  int sock, rc;
  int auth_pw = 0;
  char *userauthlist;
  LIBSSH2_SESSION *session;

  // sock创建过程,省略
  // sock = socket, connect

  session = libssh2_session_init ();
  if (session == NULL)
    {
      return -1;
    }

  libssh2_session_set_blocking (session, 1);

  rc = libssh2_session_handshake (session, sock);
  if (rc)
    {
      fprintf (stderr, "Failure establishing SSH session: %d\n", rc);
      libss2_session_free (session);
      close (sock);
      return -1;
    }

  userauthlist = libssh2_userauth_list (session, username,
                                        (unsigned int)strlen (username));
  if (userauthlist)
    {
      if (strstr (userauthlist, "password"))
        {
          auth_pw |= 1;
        }
      if (strstr (userauthlist, "publickey"))
        {
          auth_pw |= 4;
        }

      if ((auth_pw & 1) && username && pass)
        {
          auth_pw = 1;
        }
      if ((auth_pw & 4) && sshkey_pub && sshkey_pri)
        {
          auth_pw = 4;
        }

      if (auth_pw == 1)
        {
          if (libssh2_userauth_password (session, username, pass))
            {
              libss2_session_free (session);
              close (sock);
              return -1;
            }
        }
      else if (auth_pw == 4)
        {
          if (libssh2_userauth_publickey_fromfile (
                  session, username, sshkey_pub,
                  sshkey_pri, pass))
            {
              libss2_session_free (session);
              close (sock);
              return -1;
            }
        }
      else
        {
          fprintf (stderr, "No supported authentication methods found!\n");
          libss2_session_free (session);
          close (sock);
          return -1;
        }
    }

断开连接

static void
ssh2_disconnect (LIBSSH2_SESSION *session, int sock)
{
  libssh2_session_disconnect (session, "Normal Shutdown");
  libssh2_session_free (session);
  close (sock);
}

获取目录列表

使用libssh2获取目录列表,需要先创建一个LIBSSH2_SFTP的Session,在这个Session上打开目录句柄LIBSSH2_SFTP_HANDLE,之后像使用本地目录句柄一样,依次读取目录中的节点。

创建、销毁LIBSS2_SFTP以及获取错误的函数分别为:

LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session);  
LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp);  
LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp);

打开、关闭目录句柄的函数分别为:

LIBSSH2_SFTP_HANDLE * libssh2_sftp_opendir(LIBSSH2_SFTP *sftp, 
                                            const char *filename);
int libssh2_sftp_closedir(LIBSSH2_SFTP_HANDLE *handle);

读取目录节点的函数为:

int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, \  
                                       char *buffer, size_t buffer_maxlen,
                                       LIBSSH2_SFTP_ATTRIBUTES *attrs);

对于每一个读取出来的节点,使用LIBSSH2_SFTP_ATTRIBUTES结构可以获取节点的信息。

这个结构的定义为:

struct _LIBSSH2_SFTP_ATTRIBUTES {  
   /* If flags & ATTR_* bit is set, then the value in this struct will be  
    * meaningful Otherwise it should be ignored  
    */  
   unsigned long flags;  
  
   libssh2_uint64_t filesize;  
   unsigned long uid, gid;  
   unsigned long permissions;  
   unsigned long atime, mtime;  
};

如:在falgs得到了permissions的情况下,可以通过

#define LIBSSH2_SFTP_S_ISLNK(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFLNK)  
#define LIBSSH2_SFTP_S_ISREG(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFREG)  
#define LIBSSH2_SFTP_S_ISDIR(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFDIR)  
#define LIBSSH2_SFTP_S_ISCHR(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFCHR)  
#define LIBSSH2_SFTP_S_ISBLK(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFBLK)  
#define LIBSSH2_SFTP_S_ISFIFO(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFIFO)  
#define LIBSSH2_SFTP_S_ISSOCK(m) \  
   (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFSOCK)

这些宏来判断节点的文件类型。

以下是一个获取目录节点的例子:

int
ssh2_list_dir (LIBSSH2_SESSION *session, const char *dir)
{
  LIBSSH2_SFTP *sftp;
  LIBSSH2_SFTP_HANDLE *handle;
  LIBSSH2_SFTP_ATTRIBUTES attrs[1];
  struct sftp_node *node;
  char mem[512];
  int rc;

  sftp = libssh2_sftp_init (session);
  
  handle = libssh2_sftp_opendir (sftp, dir);
  if (handle == NULL)
    {
      return -1;
    }

  do
    {
      rc = libssh2_sftp_readdir (handle, mem, sizeof (mem), attrs);
      if (rc > 0)
        {
          if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS)
            {
              if (LIBSSH2_SFTP_S_ISDIR (attrs->permissions))
                {
                  if (strcmp (mem, ".") == 0 || strcmp (mem, "..") == 0)
                    continue;

                  printf ("got dir: %s\n", mem);
                }
              else if (LIBSSH2_SFTP_S_ISREG (attrs->permissions))
                {
                  if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE)
                    {
                      printf ("got file: %s, size: %ld\n", mem, attr->filesize);
                    }
                  else
                    {
                      printf ("got file: %s has no size\n", mem);
                    }
                }
              else
                {
                  printf ("file: %s type not supported\n", mem);
                }
            }
          else
            {
              printf ("file: %s has not permissions\n", mem);
            }
        }
    }
  while (rc > 0);

  libssh2_sftp_closedir (handle);

  libssh2_sftp_shutdown (sftp);

  return rc;
}

创建目录

创建目录使用的函数为:

int libssh2_sftp_mkdir(LIBSSH2_SFTP *sftp,  
                                     const char *path,  
                                     long mode);

如:

int
ssh2_mkdir (LIBSSH2_SFTP *sftp, const char *dir)
{
  int rc;
  
  rc = libssh2_sftp_mkdir (sftp, dir, 0777);
  if (rc != 0)
    {
      fprintf (stderr, "mkdir error: %lu\n",
             libssh2_sftp_last_error (sftp->sftp_session));
      return -1;
    }

  return 0;
}

删除目录

删除目录使用的函数为:

int libssh2_sftp_rmdir(LIBSSH2_SFTP *sftp,  
                                     const char *path);

重命名文件

重命名文件(目录)使用的函数为:

int libssh2_sftp_rename(LIBSSH2_SFTP *sftp,  
                                     const char *name,  
                                     const char *new_name);

删除文件

删除文件使用的函数为:

int libssh2_sftp_unlink(LIBSSH2_SFTP *sftp,  
                                     const char *path);

下载文件

下载或者上传文件的时候,不用使用LIBSSH2_SFTP结构,而是直接使用LIBSSH2_SESSION来创建LIBSSH2_CHANNEL。

首先打开一个接收文件句柄:

LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session,  
                                             const char *path,  
                                             struct stat *sb);

然后依次读取文件的内容:

ssize_t libssh2_channel_read(LIBSSH2_CHANNEL *channel,  
                                           char *buf,  
                                           size_t buflen);

最后关闭文件句柄:

int libssh2_channel_close(LIBSSH2_CHANNEL *channel);

以下是下载一个文件的示例:

int
ssh2_download_file (LIBSSH2_SESSION *session, const char *path)
{
  int rc;
  FILE *fp;
  size_t got;
  libssh2_struct_stat fileinfo[1];
  LIBSSH2_CHANNEL *channel;

  fp = fopen (path, "wb");
  if (fp == NULL)
    {
      return -1;
    }

  // 打开远程文件句柄
  channel = libssh2_scp_recv2 (session, path, fileinfo);
  if (channel == NULL)
    {
      fclose (fp);
      return -1;
    }

  got = 0;
  while (got < fileinfo->st_size)
    {
      char mem[64 * 1024];
      int amount = sizeof (mem);
      ssize_t nread;

      if ((fileinfo->st_size - got) < amount)
        {
          amount = (int)(fileinfo->st_size - got);
        }

      nread = libssh2_channel_read (channel, mem, amount);
      if (nread > 0)
        {
          fwrite (mem, 1, nread, fp);
          // 为方便理解,这里忽略文件写入的错误处理。
        }
      else if (nread <= 0)
        {
          break;
        }
      got += nread;
    }

  if (got == fileinfo->st_size)
    rc = 0;
  else
    rc = -1;

  libssh2_channel_free (channel);
  libssh2_channel_close (channel);
  fclose (fp);

  return rc;
}

上传文件

上传文件与下载文件大同小异,区别是打开文件句柄的时候,使用的函数是libssh2_scp_send,另外最后关闭channel之前,执行以下这些函数:

libssh2_channel_send_eof (channel);
libssh2_channel_wait_eof (channel);
libssh2_channel_wait_closed (channel);

确保远端正常接收完毕。

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值