457 строки
12 KiB
C
457 строки
12 KiB
C
/* This is a sample implementation of a libssh based SSH server */
|
|
/*
|
|
Copyright 2003-2011 Aris Adamantiadis
|
|
|
|
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. It's not a reference on how terminal
|
|
clients must be made or how a client should react.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libssh/libssh.h>
|
|
#include <libssh/server.h>
|
|
#include <libssh/callbacks.h>
|
|
|
|
#ifdef HAVE_ARGP_H
|
|
#include <argp.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <poll.h>
|
|
#include <pty.h>
|
|
|
|
#define SSHD_USER "libssh"
|
|
#define SSHD_PASSWORD "libssh"
|
|
|
|
#ifndef KEYS_FOLDER
|
|
#ifdef _WIN32
|
|
#define KEYS_FOLDER
|
|
#else
|
|
#define KEYS_FOLDER "/etc/ssh/"
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef WITH_PCAP
|
|
const char *pcap_file="debug.server.pcap";
|
|
ssh_pcap_file pcap;
|
|
|
|
static void set_pcap(ssh_session session){
|
|
if(!pcap_file)
|
|
return;
|
|
pcap=ssh_pcap_file_new();
|
|
if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){
|
|
printf("Error opening pcap file\n");
|
|
ssh_pcap_file_free(pcap);
|
|
pcap=NULL;
|
|
return;
|
|
}
|
|
ssh_set_pcap_file(session,pcap);
|
|
}
|
|
|
|
static void cleanup_pcap(){
|
|
ssh_pcap_file_free(pcap);
|
|
pcap=NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int auth_password(const char *user, const char *password){
|
|
if(strcmp(user, SSHD_USER))
|
|
return 0;
|
|
if(strcmp(password, SSHD_PASSWORD))
|
|
return 0;
|
|
return 1; // authenticated
|
|
}
|
|
#ifdef HAVE_ARGP_H
|
|
const char *argp_program_version = "libssh server example "
|
|
SSH_STRINGIFY(LIBSSH_VERSION);
|
|
const char *argp_program_bug_address = "<libssh@libssh.org>";
|
|
|
|
/* Program documentation. */
|
|
static char doc[] = "libssh -- a Secure Shell protocol implementation";
|
|
|
|
/* A description of the arguments we accept. */
|
|
static char args_doc[] = "BINDADDR";
|
|
|
|
static int port = 22;
|
|
|
|
/* 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 the host key.",
|
|
.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 = "verbose",
|
|
.key = 'v',
|
|
.arg = NULL,
|
|
.flags = 0,
|
|
.doc = "Get verbose output.",
|
|
.group = 0
|
|
},
|
|
{NULL, 0, 0, 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;
|
|
|
|
switch (key) {
|
|
case 'p':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg);
|
|
port = atoi(arg);
|
|
break;
|
|
case 'd':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg);
|
|
break;
|
|
case 'k':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg);
|
|
break;
|
|
case 'r':
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg);
|
|
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);
|
|
}
|
|
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 */
|
|
|
|
static int authenticate(ssh_session session) {
|
|
ssh_message message;
|
|
|
|
do {
|
|
message=ssh_message_get(session);
|
|
if(!message)
|
|
break;
|
|
switch(ssh_message_type(message)){
|
|
case SSH_REQUEST_AUTH:
|
|
switch(ssh_message_subtype(message)){
|
|
case SSH_AUTH_METHOD_PASSWORD:
|
|
printf("User %s wants to auth with pass %s\n",
|
|
ssh_message_auth_user(message),
|
|
ssh_message_auth_password(message));
|
|
if(auth_password(ssh_message_auth_user(message),
|
|
ssh_message_auth_password(message))){
|
|
ssh_message_auth_reply_success(message,0);
|
|
ssh_message_free(message);
|
|
return 1;
|
|
}
|
|
ssh_message_auth_set_methods(message,
|
|
SSH_AUTH_METHOD_PASSWORD |
|
|
SSH_AUTH_METHOD_INTERACTIVE);
|
|
// not authenticated, send default message
|
|
ssh_message_reply_default(message);
|
|
break;
|
|
|
|
case SSH_AUTH_METHOD_NONE:
|
|
default:
|
|
printf("User %s wants to auth with unknown auth %d\n",
|
|
ssh_message_auth_user(message),
|
|
ssh_message_subtype(message));
|
|
ssh_message_auth_set_methods(message,
|
|
SSH_AUTH_METHOD_PASSWORD |
|
|
SSH_AUTH_METHOD_INTERACTIVE);
|
|
ssh_message_reply_default(message);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
ssh_message_auth_set_methods(message,
|
|
SSH_AUTH_METHOD_PASSWORD |
|
|
SSH_AUTH_METHOD_INTERACTIVE);
|
|
ssh_message_reply_default(message);
|
|
}
|
|
ssh_message_free(message);
|
|
} while (1);
|
|
return 0;
|
|
}
|
|
|
|
static int copy_fd_to_chan(socket_t fd, int revents, void *userdata) {
|
|
ssh_channel chan = (ssh_channel)userdata;
|
|
char buf[2048];
|
|
int sz = 0;
|
|
|
|
if(!chan) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if(revents & POLLIN) {
|
|
sz = read(fd, buf, 2048);
|
|
if(sz > 0) {
|
|
ssh_channel_write(chan, buf, sz);
|
|
}
|
|
}
|
|
if(revents & POLLHUP) {
|
|
ssh_channel_close(chan);
|
|
sz = -1;
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
static int copy_chan_to_fd(ssh_session session,
|
|
ssh_channel channel,
|
|
void *data,
|
|
uint32_t len,
|
|
int is_stderr,
|
|
void *userdata) {
|
|
int fd = *(int*)userdata;
|
|
int sz;
|
|
(void)session;
|
|
(void)channel;
|
|
(void)is_stderr;
|
|
|
|
sz = write(fd, data, len);
|
|
return sz;
|
|
}
|
|
|
|
static void chan_close(ssh_session session, ssh_channel channel, void *userdata) {
|
|
int fd = *(int*)userdata;
|
|
(void)session;
|
|
(void)channel;
|
|
|
|
close(fd);
|
|
}
|
|
|
|
struct ssh_channel_callbacks_struct cb = {
|
|
.channel_data_function = copy_chan_to_fd,
|
|
.channel_eof_function = chan_close,
|
|
.channel_close_function = chan_close,
|
|
.userdata = NULL
|
|
};
|
|
|
|
static int main_loop(ssh_channel chan) {
|
|
ssh_session session = ssh_channel_get_session(chan);
|
|
socket_t fd;
|
|
struct termios *term = NULL;
|
|
struct winsize *win = NULL;
|
|
pid_t childpid;
|
|
ssh_event event;
|
|
short events;
|
|
|
|
|
|
childpid = forkpty(&fd, NULL, term, win);
|
|
if(childpid == 0) {
|
|
execl("/bin/bash", "/bin/bash", (char *)NULL);
|
|
abort();
|
|
}
|
|
|
|
cb.userdata = &fd;
|
|
ssh_callbacks_init(&cb);
|
|
ssh_set_channel_callbacks(chan, &cb);
|
|
|
|
events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
|
|
|
|
event = ssh_event_new();
|
|
if(event == NULL) {
|
|
printf("Couldn't get a event\n");
|
|
return -1;
|
|
}
|
|
if(ssh_event_add_fd(event, fd, events, copy_fd_to_chan, chan) != SSH_OK) {
|
|
printf("Couldn't add an fd to the event\n");
|
|
ssh_event_free(event);
|
|
return -1;
|
|
}
|
|
if(ssh_event_add_session(event, session) != SSH_OK) {
|
|
printf("Couldn't add the session to the event\n");
|
|
ssh_event_remove_fd(event, fd);
|
|
ssh_event_free(event);
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
ssh_event_dopoll(event, 1000);
|
|
} while(!ssh_channel_is_closed(chan));
|
|
|
|
ssh_event_remove_fd(event, fd);
|
|
|
|
ssh_event_remove_session(event, session);
|
|
|
|
ssh_event_free(event);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv){
|
|
ssh_session session;
|
|
ssh_bind sshbind;
|
|
ssh_message message;
|
|
ssh_channel chan=0;
|
|
int auth=0;
|
|
int shell=0;
|
|
int r;
|
|
|
|
sshbind=ssh_bind_new();
|
|
session=ssh_new();
|
|
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY,
|
|
KEYS_FOLDER "ssh_host_dsa_key");
|
|
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY,
|
|
KEYS_FOLDER "ssh_host_rsa_key");
|
|
|
|
#ifdef HAVE_ARGP_H
|
|
/*
|
|
* Parse our arguments; every option seen by parse_opt will
|
|
* be reflected in arguments.
|
|
*/
|
|
argp_parse (&argp, argc, argv, 0, 0, sshbind);
|
|
#else
|
|
(void) argc;
|
|
(void) argv;
|
|
#endif
|
|
#ifdef WITH_PCAP
|
|
set_pcap(session);
|
|
#endif
|
|
|
|
if(ssh_bind_listen(sshbind)<0){
|
|
printf("Error listening to socket: %s\n", ssh_get_error(sshbind));
|
|
return 1;
|
|
}
|
|
printf("Started sample libssh sshd on port %d\n", port);
|
|
printf("You can login as the user %s with the password %s\n", SSHD_USER,
|
|
SSHD_PASSWORD);
|
|
r = ssh_bind_accept(sshbind, session);
|
|
if(r==SSH_ERROR){
|
|
printf("Error accepting a connection: %s\n", ssh_get_error(sshbind));
|
|
return 1;
|
|
}
|
|
if (ssh_handle_key_exchange(session)) {
|
|
printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session));
|
|
return 1;
|
|
}
|
|
|
|
/* proceed to authentication */
|
|
auth = authenticate(session);
|
|
if(!auth){
|
|
printf("Authentication error: %s\n", ssh_get_error(session));
|
|
ssh_disconnect(session);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* wait for a channel session */
|
|
do {
|
|
message = ssh_message_get(session);
|
|
if(message){
|
|
if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN &&
|
|
ssh_message_subtype(message) == SSH_CHANNEL_SESSION) {
|
|
chan = ssh_message_channel_request_open_reply_accept(message);
|
|
ssh_message_free(message);
|
|
break;
|
|
} else {
|
|
ssh_message_reply_default(message);
|
|
ssh_message_free(message);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while(!chan);
|
|
|
|
if(!chan) {
|
|
printf("Error: cleint did not ask for a channel session (%s)\n",
|
|
ssh_get_error(session));
|
|
ssh_finalize();
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* wait for a shell */
|
|
do {
|
|
message = ssh_message_get(session);
|
|
if(message != NULL) {
|
|
if(ssh_message_type(message) == SSH_REQUEST_CHANNEL) {
|
|
if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) {
|
|
shell = 1;
|
|
ssh_message_channel_request_reply_success(message);
|
|
ssh_message_free(message);
|
|
break;
|
|
} else if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_PTY) {
|
|
ssh_message_channel_request_reply_success(message);
|
|
ssh_message_free(message);
|
|
continue;
|
|
}
|
|
}
|
|
ssh_message_reply_default(message);
|
|
ssh_message_free(message);
|
|
} else {
|
|
break;
|
|
}
|
|
} while(!shell);
|
|
|
|
if(!shell) {
|
|
printf("Error: No shell requested (%s)\n", ssh_get_error(session));
|
|
return 1;
|
|
}
|
|
|
|
printf("it works !\n");
|
|
|
|
main_loop(chan);
|
|
|
|
ssh_disconnect(session);
|
|
ssh_bind_free(sshbind);
|
|
#ifdef WITH_PCAP
|
|
cleanup_pcap();
|
|
#endif
|
|
ssh_finalize();
|
|
return 0;
|
|
}
|
|
|