mirror of
https://git.libssh.org/projects/libssh.git
synced 2025-11-27 13:21:11 +03:00
This allows the libssh to build with more memory-strict compliers. Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org> Reviewed-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
1582 lines
40 KiB
C
1582 lines
40 KiB
C
/* This is a sample implementation of a libssh based SSH server */
|
|
/*
|
|
Copyright 2014 Audrius Butkevicius
|
|
|
|
This file is part of the SSH Library
|
|
|
|
You are free to copy this file, modify it in any way, consider it being public
|
|
domain. This does not apply to the rest of the library though, but it is
|
|
allowed to cut-and-paste working code from this file to any license of
|
|
program.
|
|
The goal is to show the API in action.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libssh/callbacks.h>
|
|
#include <libssh/server.h>
|
|
#include <libssh/sftp.h>
|
|
|
|
#include <poll.h>
|
|
#ifdef HAVE_ARGP_H
|
|
#include <argp.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#ifdef HAVE_LIBUTIL_H
|
|
#include <libutil.h>
|
|
#endif
|
|
#ifdef HAVE_PTY_H
|
|
#include <pty.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UTMP_H
|
|
#include <utmp.h>
|
|
#endif
|
|
#ifdef HAVE_UTIL_H
|
|
#include <util.h>
|
|
#endif
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
|
|
/* below are for sftp */
|
|
#include <sys/statvfs.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <time.h>
|
|
#include <inttypes.h>
|
|
|
|
#ifndef KEYS_FOLDER
|
|
#ifdef _WIN32
|
|
#define KEYS_FOLDER
|
|
#else
|
|
#define KEYS_FOLDER "/etc/ssh/"
|
|
#endif
|
|
#endif
|
|
|
|
#define USER "myuser"
|
|
#define PASS "mypassword"
|
|
#define BUF_SIZE 1048576
|
|
#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR)
|
|
|
|
#define MAX_HANDLE_NUM 10
|
|
#define MAX_ENTRIES_NUM_IN_PACKET 50
|
|
#define MAX_LONG_NAME_LEN 350
|
|
|
|
#define SSH_SFTP_CALLBACK(name) \
|
|
static int name(sftp_client_message message)
|
|
|
|
typedef int (*client_message_callback)(sftp_client_message message);
|
|
|
|
struct message_handler
|
|
{
|
|
const char *name;
|
|
const char *extended_name;
|
|
u_int type;
|
|
|
|
client_message_callback cb;
|
|
};
|
|
|
|
struct sftp_handle
|
|
{
|
|
uint8_t type;
|
|
int fd;
|
|
DIR *dirp;
|
|
char *name;
|
|
void *session_id;
|
|
};
|
|
|
|
enum handle_type
|
|
{
|
|
NULL_HANDLE,
|
|
DIR_HANDLE,
|
|
FILE_HANDLE
|
|
};
|
|
|
|
SSH_SFTP_CALLBACK(process_unsupposed);
|
|
SSH_SFTP_CALLBACK(process_open);
|
|
SSH_SFTP_CALLBACK(process_read);
|
|
SSH_SFTP_CALLBACK(process_write);
|
|
SSH_SFTP_CALLBACK(process_close);
|
|
SSH_SFTP_CALLBACK(process_opendir);
|
|
SSH_SFTP_CALLBACK(process_readdir);
|
|
SSH_SFTP_CALLBACK(process_rmdir);
|
|
SSH_SFTP_CALLBACK(process_mkdir);
|
|
SSH_SFTP_CALLBACK(process_lstat);
|
|
SSH_SFTP_CALLBACK(process_readlink);
|
|
SSH_SFTP_CALLBACK(process_symlink);
|
|
SSH_SFTP_CALLBACK(process_remove);
|
|
SSH_SFTP_CALLBACK(process_extended_statvfs);
|
|
|
|
const struct message_handler message_handlers[] = {
|
|
{"open", NULL, SSH_FXP_OPEN, process_open},
|
|
{"close", NULL, SSH_FXP_CLOSE, process_close},
|
|
{"read", NULL, SSH_FXP_READ, process_read},
|
|
{"write", NULL, SSH_FXP_WRITE, process_write},
|
|
{"lstat", NULL, SSH_FXP_LSTAT, process_lstat},
|
|
{"fstat", NULL, SSH_FXP_FSTAT, process_unsupposed},
|
|
{"setstat", NULL, SSH_FXP_SETSTAT, process_unsupposed},
|
|
{"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupposed},
|
|
{"opendir", NULL, SSH_FXP_OPENDIR, process_opendir},
|
|
{"readdir", NULL, SSH_FXP_READDIR, process_readdir},
|
|
{"remove", NULL, SSH_FXP_REMOVE, process_remove},
|
|
{"mkdir", NULL, SSH_FXP_MKDIR, process_mkdir},
|
|
{"rmdir", NULL, SSH_FXP_RMDIR, process_rmdir},
|
|
{"realpath", NULL, SSH_FXP_REALPATH, process_unsupposed},
|
|
{"stat", NULL, SSH_FXP_STAT, process_unsupposed},
|
|
{"rename", NULL, SSH_FXP_RENAME, process_unsupposed},
|
|
{"readlink", NULL, SSH_FXP_READLINK, process_readlink},
|
|
{"symlink", NULL, SSH_FXP_SYMLINK, process_symlink},
|
|
{"init", NULL, SSH_FXP_INIT, sftp_process_init_packet},
|
|
{NULL, NULL, 0, NULL}};
|
|
|
|
const struct message_handler extended_handlers[] = {
|
|
/* here are some extended type handlers */
|
|
{"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs},
|
|
{NULL, NULL, 0, NULL}};
|
|
|
|
struct sftp_handle s_handle_table[MAX_HANDLE_NUM];
|
|
|
|
static void init_handle_table(void)
|
|
{
|
|
int obj_size = sizeof(struct sftp_handle);
|
|
memset(s_handle_table, 0, obj_size * MAX_HANDLE_NUM);
|
|
}
|
|
|
|
static void reinit_single_handle(struct sftp_handle *handle)
|
|
{
|
|
handle->type = NULL_HANDLE;
|
|
handle->session_id = NULL;
|
|
handle->dirp = NULL;
|
|
handle->name = NULL;
|
|
handle->fd = -1;
|
|
}
|
|
|
|
static int handle_is_ok(uint8_t i, int type)
|
|
{
|
|
return i < MAX_HANDLE_NUM && s_handle_table[i].type == type;
|
|
}
|
|
|
|
static int handle_close(uint8_t handle_ind)
|
|
{
|
|
int ret = SSH_ERROR;
|
|
|
|
if (handle_is_ok(handle_ind, FILE_HANDLE))
|
|
{
|
|
close(s_handle_table[handle_ind].fd);
|
|
ret = SSH_OK;
|
|
}
|
|
else if (handle_is_ok(handle_ind, DIR_HANDLE))
|
|
{
|
|
closedir((DIR *)s_handle_table[handle_ind].dirp);
|
|
ret = SSH_OK;
|
|
}
|
|
else if (handle_is_ok(handle_ind, NULL_HANDLE))
|
|
{
|
|
ret = SSH_OK;
|
|
}
|
|
|
|
if (s_handle_table[handle_ind].name != NULL)
|
|
{
|
|
free(s_handle_table[handle_ind].name);
|
|
s_handle_table[handle_ind].name = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int handle_close_by_pointer(struct sftp_handle *handle)
|
|
{
|
|
if (handle->type == NULL_HANDLE)
|
|
return -1;
|
|
|
|
if (handle->fd > 0)
|
|
close(handle->fd);
|
|
|
|
if (handle->dirp != NULL)
|
|
closedir(handle->dirp);
|
|
|
|
if (handle->name != NULL)
|
|
{
|
|
free(handle->name);
|
|
handle->name = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_handles(void)
|
|
{
|
|
uint8_t i;
|
|
for (i = 0; i < MAX_HANDLE_NUM; i++)
|
|
{
|
|
handle_close(i);
|
|
/* reinit this handle */
|
|
reinit_single_handle(&s_handle_table[i]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int add_handle(int type, void *dirp, int fd, const char *name, void *session_id)
|
|
{
|
|
int ret = -1;
|
|
uint8_t i;
|
|
if (dirp == NULL && fd < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < MAX_HANDLE_NUM; i++)
|
|
{
|
|
if (s_handle_table[i].type == NULL_HANDLE)
|
|
{
|
|
s_handle_table[i].type = type;
|
|
s_handle_table[i].session_id = session_id;
|
|
s_handle_table[i].fd = fd;
|
|
s_handle_table[i].dirp = dirp;
|
|
s_handle_table[i].name = malloc((strlen(name) + 1) * sizeof(char));
|
|
strcpy(s_handle_table[i].name, name);
|
|
|
|
ret = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret == SSH_ERROR)
|
|
printf("no other space for new handle\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *get_handle_name(struct sftp_handle *handle)
|
|
{
|
|
if (handle != NULL && handle->name != NULL)
|
|
return handle->name;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *ssh_str_error(int u_errno)
|
|
{
|
|
switch (u_errno)
|
|
{
|
|
case SSH_FX_NO_SUCH_FILE:
|
|
return "No such file";
|
|
case SSH_FX_PERMISSION_DENIED:
|
|
return "Permission denied";
|
|
case SSH_FX_BAD_MESSAGE:
|
|
return "Bad message";
|
|
case SSH_FX_OP_UNSUPPORTED:
|
|
return "Operation not supported";
|
|
default:
|
|
return "Operation failed";
|
|
}
|
|
return "Operation failed";
|
|
}
|
|
|
|
static int unix_errno_to_ssh_stat(int u_errno)
|
|
{
|
|
int ret = SSH_OK;
|
|
switch (u_errno)
|
|
{
|
|
case 0:
|
|
break;
|
|
case ENOENT:
|
|
case ENOTDIR:
|
|
case EBADF:
|
|
case ELOOP:
|
|
ret = SSH_FX_NO_SUCH_FILE;
|
|
break;
|
|
case EPERM:
|
|
case EACCES:
|
|
case EFAULT:
|
|
ret = SSH_FX_PERMISSION_DENIED;
|
|
break;
|
|
case ENAMETOOLONG:
|
|
case EINVAL:
|
|
ret = SSH_FX_BAD_MESSAGE;
|
|
break;
|
|
case ENOSYS:
|
|
ret = SSH_FX_OP_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
ret = SSH_FX_FAILURE;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct *z_attr)
|
|
{
|
|
z_attr->flags = 0 | (uint32_t)SSH_FILEXFER_ATTR_SIZE;
|
|
z_attr->size = z_st->st_size;
|
|
|
|
z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_UIDGID;
|
|
z_attr->uid = z_st->st_uid;
|
|
z_attr->gid = z_st->st_gid;
|
|
|
|
z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS;
|
|
z_attr->permissions = z_st->st_mode;
|
|
|
|
z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_ACMODTIME;
|
|
z_attr->atime = z_st->st_atime;
|
|
z_attr->mtime = z_st->st_mtime;
|
|
}
|
|
|
|
static void clear_filexfer_attrib(struct sftp_attributes_struct *z_attr)
|
|
{
|
|
z_attr->flags = 0;
|
|
z_attr->size = 0;
|
|
z_attr->uid = 0;
|
|
z_attr->gid = 0;
|
|
z_attr->permissions = 0;
|
|
z_attr->atime = 0;
|
|
z_attr->mtime = 0;
|
|
}
|
|
|
|
static int readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name)
|
|
{
|
|
char tmpbuf[MAX_LONG_NAME_LEN];
|
|
char time[50];
|
|
char *ptr = z_long_name;
|
|
int mode = z_st->st_mode;
|
|
|
|
*ptr = '\0';
|
|
|
|
switch (mode & S_IFMT)
|
|
{
|
|
case S_IFDIR:
|
|
{
|
|
*ptr++ = 'd';
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
*ptr++ = '-';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* user */
|
|
if (mode & 0400)
|
|
*ptr++ = 'r';
|
|
else
|
|
*ptr++ = '-';
|
|
|
|
if (mode & 0200)
|
|
*ptr++ = 'w';
|
|
else
|
|
*ptr++ = '-';
|
|
|
|
if (mode & 0100)
|
|
{
|
|
if (mode & S_ISUID)
|
|
*ptr++ = 's';
|
|
else
|
|
*ptr++ = 'x';
|
|
}
|
|
else
|
|
*ptr++ = '-';
|
|
|
|
/* group */
|
|
if (mode & 040)
|
|
*ptr++ = 'r';
|
|
else
|
|
*ptr++ = '-';
|
|
if (mode & 020)
|
|
*ptr++ = 'w';
|
|
else
|
|
*ptr++ = '-';
|
|
if (mode & 010)
|
|
*ptr++ = 'x';
|
|
else
|
|
*ptr++ = '-';
|
|
|
|
/* other */
|
|
if (mode & 04)
|
|
*ptr++ = 'r';
|
|
else
|
|
*ptr++ = '-';
|
|
if (mode & 02)
|
|
*ptr++ = 'w';
|
|
else
|
|
*ptr++ = '-';
|
|
if (mode & 01)
|
|
*ptr++ = 'x';
|
|
else
|
|
*ptr++ = '-';
|
|
|
|
*ptr++ = ' ';
|
|
*ptr = '\0';
|
|
|
|
snprintf(tmpbuf, sizeof(tmpbuf), "%3d %d %d %d", (int)z_st->st_nlink,
|
|
(int)z_st->st_uid, (int)z_st->st_gid, (int)z_st->st_size);
|
|
strcat(z_long_name, tmpbuf);
|
|
|
|
ctime_r(&z_st->st_mtime, time);
|
|
if ((ptr = strchr(time, '\n')))
|
|
{
|
|
*ptr = '\0';
|
|
}
|
|
snprintf(tmpbuf, sizeof(tmpbuf), " %s %s", time + 4, z_file_name);
|
|
strcat(z_long_name, tmpbuf);
|
|
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_open(sftp_client_message client_msg)
|
|
{
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
uint32_t msg_flag = sftp_client_message_get_flags(client_msg);
|
|
int file_flag;
|
|
int fd = -1;
|
|
int handle_ind = -1;
|
|
int status;
|
|
|
|
if (((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) &&
|
|
((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE))
|
|
{
|
|
file_flag = O_RDWR; // file must exist
|
|
if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT)
|
|
file_flag |= O_CREAT;
|
|
}
|
|
else if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE)
|
|
{
|
|
file_flag = O_WRONLY;
|
|
if ((msg_flag & (uint32_t)SSH_FXF_APPEND) == SSH_FXF_APPEND)
|
|
file_flag |= O_APPEND;
|
|
if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT)
|
|
file_flag |= O_CREAT;
|
|
}
|
|
else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ)
|
|
{
|
|
file_flag = O_RDONLY;
|
|
}
|
|
else
|
|
{
|
|
printf("undefined message flag\n");
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, "Flag error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
fd = open(filename, file_flag, 0600);
|
|
if (fd == -1)
|
|
{
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
printf("error opening file with error: %d\n", errno);
|
|
sftp_reply_status(client_msg, status, "Write error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
handle_ind = add_handle(FILE_HANDLE, NULL, fd, filename, client_msg->sftp);
|
|
if (handle_ind >= 0)
|
|
{
|
|
void *handle_ptr = &s_handle_table[handle_ind];
|
|
ssh_string handle_s = sftp_handle_alloc(client_msg->sftp, handle_ptr);
|
|
sftp_reply_handle(client_msg, handle_s);
|
|
ssh_string_free(handle_s);
|
|
}
|
|
else
|
|
{
|
|
close(fd);
|
|
printf("opening file failed");
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available");
|
|
}
|
|
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_read(sftp_client_message client_msg)
|
|
{
|
|
struct sftp_handle *client_handle = (struct sftp_handle *)sftp_handle(client_msg->sftp, client_msg->handle);
|
|
uint32_t readn;
|
|
int fd;
|
|
char *buffer;
|
|
int rv;
|
|
|
|
if (client_handle == NULL || client_handle->session_id != client_msg->sftp)
|
|
{
|
|
printf("got wrong handle from msg\n");
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
fd = client_handle->fd;
|
|
|
|
if (fd < 0)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
printf("error reading file fd: %d\n", fd);
|
|
return SSH_ERROR;
|
|
}
|
|
rv = lseek(fd, client_msg->offset, SEEK_SET);
|
|
if (rv == -1)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
printf("error seeking file fd: %d at offset: %" PRIu64 "\n", fd, client_msg->offset);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
buffer = malloc(client_msg->len);
|
|
readn = read(fd, buffer, client_msg->len);
|
|
|
|
if (readn > 0)
|
|
{
|
|
sftp_reply_data(client_msg, buffer, readn);
|
|
}
|
|
else if (readn == 0)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_EOF, "EOF encountered");
|
|
}
|
|
else
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
printf("read file error!\n");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
free(buffer);
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_write(sftp_client_message client_msg)
|
|
{
|
|
struct sftp_handle *client_handle = (struct sftp_handle *)sftp_handle(client_msg->sftp, client_msg->handle);
|
|
int written;
|
|
int fd;
|
|
const char *msg_data;
|
|
uint32_t len;
|
|
int rv;
|
|
|
|
if (client_handle == NULL || client_handle->session_id != client_msg->sftp)
|
|
{
|
|
printf("get wrong handle from msg\n");
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
fd = client_handle->fd;
|
|
|
|
if (fd < 0)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
printf("write file fd error!\n");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
msg_data = ssh_string_get_char(client_msg->data);
|
|
len = ssh_string_len(client_msg->data);
|
|
|
|
rv = lseek(fd, client_msg->offset, SEEK_SET);
|
|
if (rv == -1)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
printf("error seeking file at offset: %" PRIu64 "\n", client_msg->offset);
|
|
}
|
|
written = write(fd, msg_data, len);
|
|
if (written == (int)len)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_OK, NULL);
|
|
}
|
|
else if (written == -1)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error");
|
|
}
|
|
else
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, "Partial write");
|
|
}
|
|
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_close(sftp_client_message client_msg)
|
|
{
|
|
struct sftp_handle *client_handle = (struct sftp_handle *)sftp_handle(client_msg->sftp, client_msg->handle);
|
|
int ret;
|
|
|
|
if (client_handle == NULL)
|
|
{
|
|
printf("get wrong handle from msg\n");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
ret = handle_close_by_pointer(client_handle);
|
|
reinit_single_handle(client_handle);
|
|
if (ret == SSH_OK)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_OK, NULL);
|
|
}
|
|
else
|
|
{
|
|
printf("closing file failed\n");
|
|
sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle");
|
|
}
|
|
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_opendir(sftp_client_message client_msg)
|
|
{
|
|
DIR *dir = NULL;
|
|
const char *dir_name = sftp_client_message_get_filename(client_msg);
|
|
int handle_ind = -1;
|
|
|
|
dir = opendir(dir_name);
|
|
if (dir == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
handle_ind = add_handle(DIR_HANDLE, dir, -1, dir_name, client_msg->sftp);
|
|
if (handle_ind >= 0)
|
|
{
|
|
ssh_string handle_s = sftp_handle_alloc(client_msg->sftp, &s_handle_table[handle_ind]);
|
|
sftp_reply_handle(client_msg, handle_s);
|
|
ssh_string_free(handle_s);
|
|
}
|
|
else
|
|
{
|
|
closedir(dir);
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available");
|
|
}
|
|
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_readdir(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
struct sftp_handle *client_handle = (struct sftp_handle *)sftp_handle(client_msg->sftp, client_msg->handle);
|
|
int entries = 0;
|
|
struct dirent *dentry;
|
|
DIR *dir = NULL;
|
|
|
|
char long_path[PATH_MAX];
|
|
int srclen;
|
|
char *handle_name;
|
|
|
|
if (client_handle == NULL || client_handle->session_id != client_msg->sftp)
|
|
{
|
|
printf("get wrong handle from msg\n");
|
|
sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
dir = client_handle->dirp;
|
|
if (dir == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
printf("read dir handle error!\n");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
handle_name = get_handle_name(client_handle);
|
|
if (handle_name == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
srclen = strlen(handle_name);
|
|
if (srclen + 2 >= PATH_MAX)
|
|
{
|
|
printf("handle string length exceed max length!\n");
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_ENTRIES_NUM_IN_PACKET; i++)
|
|
{
|
|
dentry = readdir(dir);
|
|
|
|
if (dentry != NULL)
|
|
{
|
|
struct sftp_attributes_struct attr;
|
|
struct stat st;
|
|
char long_name[MAX_LONG_NAME_LEN];
|
|
|
|
if (strlen(dentry->d_name) + srclen + 1 >= PATH_MAX)
|
|
{
|
|
printf("handle string length exceed max length!\n");
|
|
sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL);
|
|
return SSH_ERROR;
|
|
}
|
|
snprintf(long_path, PATH_MAX, "%s/%s", handle_name, dentry->d_name);
|
|
|
|
if (lstat(long_path, &st) == 0)
|
|
{
|
|
stat_to_filexfer_attrib(&st, &attr);
|
|
}
|
|
else
|
|
{
|
|
clear_filexfer_attrib(&attr);
|
|
}
|
|
|
|
if (readdir_long_name(dentry->d_name, &st, long_name) == 0)
|
|
{
|
|
sftp_reply_names_add(client_msg, dentry->d_name, long_name, &attr);
|
|
}
|
|
else
|
|
{
|
|
printf("readdir long name error\n");
|
|
}
|
|
|
|
entries++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entries > 0)
|
|
{
|
|
ret = sftp_reply_names(client_msg);
|
|
}
|
|
else
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_EOF, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_mkdir(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
uint32_t msg_flags = client_msg->flags;
|
|
uint32_t permission = client_msg->attr->permissions;
|
|
uint32_t mode = (msg_flags & (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS) ? permission & (uint32_t)07777 : 0777;
|
|
int status = SSH_FX_OK;
|
|
int rv;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
rv = mkdir(filename, mode);
|
|
if (rv < 0)
|
|
{
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
ret = SSH_ERROR;
|
|
}
|
|
|
|
sftp_reply_status(client_msg, status, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_rmdir(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
int status = SSH_FX_OK;
|
|
int rv;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
rv = rmdir(filename);
|
|
if (rv < 0)
|
|
{
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
ret = SSH_ERROR;
|
|
}
|
|
|
|
sftp_reply_status(client_msg, status, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_lstat(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
struct sftp_attributes_struct attr;
|
|
struct stat st;
|
|
int status = SSH_FX_OK;
|
|
int rv;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
rv = lstat(filename, &st);
|
|
if (rv < 0)
|
|
{
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
sftp_reply_status(client_msg, status, NULL);
|
|
ret = SSH_ERROR;
|
|
}
|
|
else
|
|
{
|
|
stat_to_filexfer_attrib(&st, &attr);
|
|
sftp_reply_attr(client_msg, &attr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_readlink(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
char buf[PATH_MAX];
|
|
int len = -1;
|
|
const char *err_msg;
|
|
int status = SSH_FX_OK;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
len = readlink(filename, buf, sizeof(buf) - 1);
|
|
if (len < 0)
|
|
{
|
|
printf("read link error with reason: %d\n", errno);
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
err_msg = ssh_str_error(status);
|
|
sftp_reply_status(client_msg, status, err_msg);
|
|
ret = SSH_ERROR;
|
|
}
|
|
else
|
|
{
|
|
buf[len] = '\0';
|
|
sftp_reply_name(client_msg, buf, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_symlink(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *destpath = sftp_client_message_get_filename(client_msg);
|
|
const char *srcpath = ssh_string_get_char(client_msg->data);
|
|
int status = SSH_FX_OK;
|
|
int rv;
|
|
// printf("try to create link with src: %s and dest: %s \n", srcpath, destpath);
|
|
|
|
if (srcpath == NULL || destpath == NULL)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
rv = symlink(srcpath, destpath);
|
|
if (rv < 0)
|
|
{
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
printf("error symlink with error: %d\n", errno);
|
|
sftp_reply_status(client_msg, status, "Write error");
|
|
ret = SSH_ERROR;
|
|
}
|
|
else
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_OK, "write success");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_remove(sftp_client_message client_msg)
|
|
{
|
|
int ret = SSH_OK;
|
|
const char *filename = sftp_client_message_get_filename(client_msg);
|
|
int rv;
|
|
int status = SSH_FX_OK;
|
|
|
|
rv = unlink(filename);
|
|
if (rv < 0)
|
|
{
|
|
printf("unlink error with reason: %d\n", errno);
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
ret = SSH_ERROR;
|
|
}
|
|
|
|
sftp_reply_status(client_msg, status, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int process_unsupposed(sftp_client_message client_msg)
|
|
{
|
|
sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, "Operation not supported");
|
|
printf("Message type %d not implemented\n", sftp_client_message_get_type(client_msg));
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int process_extended_statvfs(sftp_client_message client_msg)
|
|
{
|
|
const char *path = sftp_client_message_get_filename(client_msg);
|
|
struct statvfs st;
|
|
int status;
|
|
int rv;
|
|
|
|
rv = statvfs(path, &st);
|
|
if (rv == 0)
|
|
{
|
|
sftp_statvfs_t sftp_statvfs;
|
|
u_int64_t flag;
|
|
|
|
sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct));
|
|
if (sftp_statvfs != NULL)
|
|
{
|
|
flag = (st.f_flag & ST_RDONLY) ? SSH_FXE_STATVFS_ST_RDONLY : 0;
|
|
flag |= (st.f_flag & ST_NOSUID) ? SSH_FXE_STATVFS_ST_NOSUID : 0;
|
|
|
|
sftp_statvfs->f_bsize = st.f_bsize;
|
|
sftp_statvfs->f_frsize = st.f_frsize;
|
|
sftp_statvfs->f_blocks = st.f_blocks;
|
|
sftp_statvfs->f_bfree = st.f_bfree;
|
|
sftp_statvfs->f_bavail = st.f_bavail;
|
|
sftp_statvfs->f_files = st.f_files;
|
|
sftp_statvfs->f_ffree = st.f_ffree;
|
|
sftp_statvfs->f_favail = st.f_favail;
|
|
sftp_statvfs->f_fsid = st.f_fsid;
|
|
sftp_statvfs->f_flag = flag;
|
|
sftp_statvfs->f_namemax = st.f_namemax;
|
|
|
|
rv = sftp_reply_statvfs(client_msg, sftp_statvfs);
|
|
free(sftp_statvfs);
|
|
if (rv == 0)
|
|
{
|
|
return SSH_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
status = unix_errno_to_ssh_stat(errno);
|
|
sftp_reply_status(client_msg, status, NULL);
|
|
|
|
printf("statvfs send failed!\n");
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
static int process_extended(sftp_client_message sftp_msg)
|
|
{
|
|
int status = SSH_ERROR;
|
|
|
|
const char *subtype = sftp_msg->submessage;
|
|
client_message_callback handler = NULL;
|
|
for (int i = 0; extended_handlers[i].cb != NULL; i++)
|
|
{
|
|
if (strcmp(subtype, extended_handlers[i].extended_name) == 0)
|
|
{
|
|
handler = extended_handlers[i].cb;
|
|
break;
|
|
}
|
|
}
|
|
if (handler != NULL)
|
|
{
|
|
status = handler(sftp_msg);
|
|
return status;
|
|
}
|
|
|
|
sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, "Extended Operation not supported");
|
|
printf("Extended Message type %s not implemented\n", subtype);
|
|
return SSH_OK;
|
|
}
|
|
|
|
static int dispatch_sftp_request(sftp_client_message sftp_msg)
|
|
{
|
|
int status = SSH_ERROR;
|
|
client_message_callback handler = NULL;
|
|
u_int sft_msg_type = sftp_client_message_get_type(sftp_msg);
|
|
|
|
for (int i = 0; message_handlers[i].cb != NULL; i++)
|
|
{
|
|
if (sft_msg_type == message_handlers[i].type)
|
|
{
|
|
handler = message_handlers[i].cb;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (handler != NULL)
|
|
{
|
|
status = handler(sftp_msg);
|
|
}
|
|
else
|
|
{
|
|
sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, "Operation not supported");
|
|
printf("Message type %d not implemented\n", sft_msg_type);
|
|
return SSH_OK;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int process_client_message(sftp_client_message client_msg)
|
|
{
|
|
int status = SSH_OK;
|
|
if (client_msg == NULL)
|
|
{
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
switch (client_msg->type)
|
|
{
|
|
case SSH_FXP_EXTENDED:
|
|
status = process_extended(client_msg);
|
|
break;
|
|
default:
|
|
status = dispatch_sftp_request(client_msg);
|
|
}
|
|
|
|
if (status != SSH_OK)
|
|
printf("error occur in process client message!\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
static void set_default_keys(ssh_bind sshbind,
|
|
int rsa_already_set,
|
|
int dsa_already_set,
|
|
int ecdsa_already_set)
|
|
{
|
|
if (!rsa_already_set)
|
|
{
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY,
|
|
KEYS_FOLDER "ssh_host_rsa_key");
|
|
}
|
|
if (!dsa_already_set)
|
|
{
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY,
|
|
KEYS_FOLDER "ssh_host_dsa_key");
|
|
}
|
|
if (!ecdsa_already_set)
|
|
{
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY,
|
|
KEYS_FOLDER "ssh_host_ecdsa_key");
|
|
}
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY,
|
|
KEYS_FOLDER "ssh_host_ed25519_key");
|
|
}
|
|
#define DEF_STR_SIZE 1024
|
|
char authorizedkeys[DEF_STR_SIZE] = {0};
|
|
#ifdef HAVE_ARGP_H
|
|
const char *argp_program_version = "libssh sftp server example " SSH_STRINGIFY(LIBSSH_VERSION);
|
|
const char *argp_program_bug_address = "<libssh@libssh.org>";
|
|
|
|
/* Program documentation. */
|
|
static char doc[] = "Sftp server implemented with libssh -- a Secure Shell protocol implementation";
|
|
|
|
/* A description of the arguments we accept. */
|
|
static char args_doc[] = "BINDADDR";
|
|
|
|
/* The options we understand. */
|
|
static struct argp_option options[] = {
|
|
{.name = "port",
|
|
.key = 'p',
|
|
.arg = "PORT",
|
|
.flags = 0,
|
|
.doc = "Set the port to bind.",
|
|
.group = 0},
|
|
{.name = "hostkey",
|
|
.key = 'k',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "Set a host key. Can be used multiple times. "
|
|
"Implies no default keys.",
|
|
.group = 0},
|
|
{.name = "dsakey",
|
|
.key = 'd',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "Set the dsa key.",
|
|
.group = 0},
|
|
{.name = "rsakey",
|
|
.key = 'r',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "Set the rsa key.",
|
|
.group = 0},
|
|
{.name = "ecdsakey",
|
|
.key = 'e',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "Set the ecdsa key.",
|
|
.group = 0},
|
|
{.name = "authorizedkeys",
|
|
.key = 'a',
|
|
.arg = "FILE",
|
|
.flags = 0,
|
|
.doc = "Set the authorized keys file.",
|
|
.group = 0},
|
|
{.name = "no-default-keys",
|
|
.key = 'n',
|
|
.arg = NULL,
|
|
.flags = 0,
|
|
.doc = "Do not set default key locations.",
|
|
.group = 0},
|
|
{.name = "verbose",
|
|
.key = 'v',
|
|
.arg = NULL,
|
|
.flags = 0,
|
|
.doc = "Get verbose output.",
|
|
.group = 0},
|
|
{NULL, 0, NULL, 0, NULL, 0}};
|
|
|
|
/* Parse a single option. */
|
|
static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
|
{
|
|
/* Get the input argument from argp_parse, which we
|
|
* know is a pointer to our arguments structure. */
|
|
ssh_bind sshbind = state->input;
|
|
static int no_default_keys = 0;
|
|
static int rsa_already_set = 0, dsa_already_set = 0, ecdsa_already_set = 0;
|
|
|
|
switch (key)
|
|
{
|
|
case 'n':
|
|
no_default_keys = 1;
|
|
break;
|
|
case 'p':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg);
|
|
break;
|
|
case 'd':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg);
|
|
dsa_already_set = 1;
|
|
break;
|
|
case 'k':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg);
|
|
/* We can't track the types of keys being added with this
|
|
option, so let's ensure we keep the keys we're adding
|
|
by just not setting the default keys */
|
|
no_default_keys = 1;
|
|
break;
|
|
case 'r':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg);
|
|
rsa_already_set = 1;
|
|
break;
|
|
case 'e':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, arg);
|
|
ecdsa_already_set = 1;
|
|
break;
|
|
case 'a':
|
|
strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1);
|
|
break;
|
|
case 'v':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR,
|
|
"3");
|
|
break;
|
|
case ARGP_KEY_ARG:
|
|
if (state->arg_num >= 1)
|
|
{
|
|
/* Too many arguments. */
|
|
argp_usage(state);
|
|
}
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg);
|
|
break;
|
|
case ARGP_KEY_END:
|
|
if (state->arg_num < 1)
|
|
{
|
|
/* Not enough arguments. */
|
|
argp_usage(state);
|
|
}
|
|
|
|
if (!no_default_keys)
|
|
{
|
|
set_default_keys(sshbind,
|
|
rsa_already_set,
|
|
dsa_already_set,
|
|
ecdsa_already_set);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
return ARGP_ERR_UNKNOWN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Our argp parser. */
|
|
static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL};
|
|
#endif /* HAVE_ARGP_H */
|
|
|
|
/* A userdata struct for channel. */
|
|
struct channel_data_struct
|
|
{
|
|
/* pid of the child process the channel will spawn. */
|
|
pid_t pid;
|
|
/* For PTY allocation */
|
|
socket_t pty_master;
|
|
socket_t pty_slave;
|
|
/* For communication with the child process. */
|
|
socket_t child_stdin;
|
|
socket_t child_stdout;
|
|
/* Only used for subsystem and exec requests. */
|
|
socket_t child_stderr;
|
|
/* Event which is used to poll the above descriptors. */
|
|
ssh_event event;
|
|
/* Terminal size struct. */
|
|
struct winsize *winsize;
|
|
sftp_session sftp;
|
|
};
|
|
|
|
/* A userdata struct for session. */
|
|
struct session_data_struct
|
|
{
|
|
/* Pointer to the channel the session will allocate. */
|
|
ssh_channel channel;
|
|
int auth_attempts;
|
|
int authenticated;
|
|
};
|
|
|
|
static int data_function(ssh_session session, ssh_channel channel, void *data,
|
|
uint32_t len, int is_stderr, void *userdata)
|
|
{
|
|
struct channel_data_struct *cdata = (struct channel_data_struct *)userdata;
|
|
sftp_session sftp = cdata->sftp;
|
|
sftp_client_message msg;
|
|
int decode_len;
|
|
int rc;
|
|
|
|
decode_len = sftp_decode_channel_data_to_packet(sftp, data);
|
|
if (decode_len == -1)
|
|
return -1;
|
|
|
|
msg = sftp_get_client_message_from_packet(sftp);
|
|
rc = process_client_message(msg);
|
|
sftp_client_message_free(msg);
|
|
if (rc != SSH_OK)
|
|
printf("process sftp failed!\n");
|
|
|
|
return decode_len;
|
|
}
|
|
|
|
static int subsystem_request(ssh_session session, ssh_channel channel,
|
|
const char *subsystem, void *userdata)
|
|
{
|
|
/* subsystem requests behave simillarly to exec requests. */
|
|
if (strcmp(subsystem, "sftp") == 0)
|
|
{
|
|
struct channel_data_struct *cdata = (struct channel_data_struct *)userdata;
|
|
|
|
/* initialize sftp session and file handler */
|
|
cdata->sftp = sftp_server_new(session, channel);
|
|
init_handle_table();
|
|
|
|
return SSH_OK;
|
|
}
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
static int auth_password(ssh_session session, const char *user,
|
|
const char *pass, void *userdata)
|
|
{
|
|
struct session_data_struct *sdata = (struct session_data_struct *)userdata;
|
|
|
|
if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0)
|
|
{
|
|
sdata->authenticated = 1;
|
|
return SSH_AUTH_SUCCESS;
|
|
}
|
|
|
|
sdata->auth_attempts++;
|
|
return SSH_AUTH_DENIED;
|
|
}
|
|
|
|
static int auth_publickey(ssh_session session,
|
|
const char *user,
|
|
struct ssh_key_struct *pubkey,
|
|
char signature_state,
|
|
void *userdata)
|
|
{
|
|
struct session_data_struct *sdata = (struct session_data_struct *)userdata;
|
|
|
|
if (signature_state == SSH_PUBLICKEY_STATE_NONE)
|
|
{
|
|
return SSH_AUTH_SUCCESS;
|
|
}
|
|
|
|
if (signature_state != SSH_PUBLICKEY_STATE_VALID)
|
|
{
|
|
return SSH_AUTH_DENIED;
|
|
}
|
|
|
|
// valid so far. Now look through authorized keys for a match
|
|
if (authorizedkeys[0])
|
|
{
|
|
ssh_key key = NULL;
|
|
int result;
|
|
struct stat buf;
|
|
|
|
if (stat(authorizedkeys, &buf) == 0)
|
|
{
|
|
result = ssh_pki_import_pubkey_file(authorizedkeys, &key);
|
|
if ((result != SSH_OK) || (key == NULL))
|
|
{
|
|
fprintf(stderr,
|
|
"Unable to import public key file %s\n",
|
|
authorizedkeys);
|
|
}
|
|
else
|
|
{
|
|
result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC);
|
|
ssh_key_free(key);
|
|
if (result == 0)
|
|
{
|
|
sdata->authenticated = 1;
|
|
return SSH_AUTH_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// no matches
|
|
sdata->authenticated = 0;
|
|
return SSH_AUTH_DENIED;
|
|
}
|
|
|
|
static ssh_channel channel_open(ssh_session session, void *userdata)
|
|
{
|
|
struct session_data_struct *sdata = (struct session_data_struct *)userdata;
|
|
|
|
sdata->channel = ssh_channel_new(session);
|
|
return sdata->channel;
|
|
}
|
|
|
|
static void handle_session(ssh_event event, ssh_session session)
|
|
{
|
|
int n;
|
|
int rc = 0;
|
|
|
|
/* Structure for storing the pty size. */
|
|
struct winsize wsize = {
|
|
.ws_row = 0,
|
|
.ws_col = 0,
|
|
.ws_xpixel = 0,
|
|
.ws_ypixel = 0};
|
|
|
|
/* Our struct holding information about the channel. */
|
|
struct channel_data_struct cdata = {
|
|
.pid = 0,
|
|
.pty_master = -1,
|
|
.pty_slave = -1,
|
|
.child_stdin = -1,
|
|
.child_stdout = -1,
|
|
.child_stderr = -1,
|
|
.event = NULL,
|
|
.winsize = &wsize,
|
|
.sftp = NULL};
|
|
|
|
/* Our struct holding information about the session. */
|
|
struct session_data_struct sdata = {
|
|
.channel = NULL,
|
|
.auth_attempts = 0,
|
|
.authenticated = 0};
|
|
|
|
struct ssh_channel_callbacks_struct channel_cb = {
|
|
.userdata = &cdata,
|
|
.channel_data_function = data_function,
|
|
.channel_subsystem_request_function = subsystem_request};
|
|
|
|
struct ssh_server_callbacks_struct server_cb = {
|
|
.userdata = &sdata,
|
|
.auth_password_function = auth_password,
|
|
.channel_open_request_session_function = channel_open,
|
|
};
|
|
|
|
if (authorizedkeys[0])
|
|
{
|
|
server_cb.auth_pubkey_function = auth_publickey;
|
|
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY);
|
|
}
|
|
else
|
|
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
|
|
|
|
ssh_callbacks_init(&server_cb);
|
|
ssh_callbacks_init(&channel_cb);
|
|
|
|
ssh_set_server_callbacks(session, &server_cb);
|
|
|
|
if (ssh_handle_key_exchange(session) != SSH_OK)
|
|
{
|
|
fprintf(stderr, "%s\n", ssh_get_error(session));
|
|
return;
|
|
}
|
|
|
|
ssh_event_add_session(event, session);
|
|
|
|
n = 0;
|
|
while (sdata.authenticated == 0 || sdata.channel == NULL)
|
|
{
|
|
/* If the user has used up all attempts, or if he hasn't been able to
|
|
* authenticate in 10 seconds (n * 100ms), disconnect. */
|
|
if (sdata.auth_attempts >= 3 || n >= 100)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ssh_event_dopoll(event, 100) == SSH_ERROR)
|
|
{
|
|
fprintf(stderr, "%s\n", ssh_get_error(session));
|
|
return;
|
|
}
|
|
n++;
|
|
}
|
|
|
|
ssh_set_channel_callbacks(sdata.channel, &channel_cb);
|
|
|
|
do
|
|
{
|
|
/* Poll the main event which takes care of the session, the channel and
|
|
* even our child process's stdout/stderr (once it's started). */
|
|
if (ssh_event_dopoll(event, -1) == SSH_ERROR)
|
|
{
|
|
ssh_channel_close(sdata.channel);
|
|
}
|
|
|
|
/* If child process's stdout/stderr has been registered with the event,
|
|
* or the child process hasn't started yet, continue. */
|
|
if (cdata.event != NULL || cdata.pid == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
} while (ssh_channel_is_open(sdata.channel) &&
|
|
(cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0));
|
|
|
|
free_handles();
|
|
|
|
ssh_channel_send_eof(sdata.channel);
|
|
ssh_channel_close(sdata.channel);
|
|
|
|
/* Wait up to 5 seconds for the client to terminate the session. */
|
|
for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++)
|
|
{
|
|
ssh_event_dopoll(event, 100);
|
|
}
|
|
}
|
|
|
|
/* SIGCHLD handler for cleaning up dead children. */
|
|
static void sigchld_handler(int signo)
|
|
{
|
|
while (waitpid(-1, NULL, WNOHANG) > 0)
|
|
;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
ssh_bind sshbind = NULL;
|
|
ssh_session session = NULL;
|
|
ssh_event event = NULL;
|
|
struct sigaction sa;
|
|
int rc;
|
|
|
|
/* Set up SIGCHLD handler. */
|
|
sa.sa_handler = sigchld_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
|
if (sigaction(SIGCHLD, &sa, NULL) != 0)
|
|
{
|
|
fprintf(stderr, "Failed to register SIGCHLD handler\n");
|
|
return 1;
|
|
}
|
|
|
|
rc = ssh_init();
|
|
if (rc < 0)
|
|
{
|
|
fprintf(stderr, "ssh_init failed\n");
|
|
goto exit;
|
|
}
|
|
|
|
sshbind = ssh_bind_new();
|
|
if (sshbind == NULL)
|
|
{
|
|
fprintf(stderr, "ssh_bind_new failed\n");
|
|
goto exit;
|
|
}
|
|
|
|
#ifdef HAVE_ARGP_H
|
|
argp_parse(&argp, argc, argv, 0, 0, sshbind);
|
|
#else
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
set_default_keys(sshbind, 0, 0, 0);
|
|
#endif /* HAVE_ARGP_H */
|
|
|
|
if (ssh_bind_listen(sshbind) < 0)
|
|
{
|
|
fprintf(stderr, "%s\n", ssh_get_error(sshbind));
|
|
goto exit;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
session = ssh_new();
|
|
if (session == NULL)
|
|
{
|
|
fprintf(stderr, "Failed to allocate session\n");
|
|
continue;
|
|
}
|
|
|
|
/* Blocks until there is a new incoming connection. */
|
|
if (ssh_bind_accept(sshbind, session) != SSH_ERROR)
|
|
{
|
|
switch (fork())
|
|
{
|
|
case 0:
|
|
/* Remove the SIGCHLD handler inherited from parent. */
|
|
sa.sa_handler = SIG_DFL;
|
|
sigaction(SIGCHLD, &sa, NULL);
|
|
/* Remove socket binding, which allows us to restart the
|
|
* parent process, without terminating existing sessions. */
|
|
ssh_bind_free(sshbind);
|
|
|
|
event = ssh_event_new();
|
|
if (event != NULL)
|
|
{
|
|
/* Blocks until the SSH session ends by either
|
|
* child process exiting, or client disconnecting. */
|
|
handle_session(event, session);
|
|
ssh_event_free(event);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Could not create polling context\n");
|
|
}
|
|
ssh_disconnect(session);
|
|
ssh_free(session);
|
|
|
|
exit(0);
|
|
case -1:
|
|
fprintf(stderr, "Failed to fork\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s\n", ssh_get_error(sshbind));
|
|
}
|
|
/* Since the session has been passed to a child fork, do some cleaning
|
|
* up at the parent process. */
|
|
ssh_disconnect(session);
|
|
ssh_free(session);
|
|
}
|
|
|
|
exit:
|
|
ssh_bind_free(sshbind);
|
|
ssh_finalize();
|
|
return 0;
|
|
}
|