Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Программирование под Unix/Linux > simple-telnetd демон


Автор: d2s1 5.3.2010, 10:43
Написал пример демона simple-telnetd, он позволяет удаленно запускать некоторое ограниченное подмножество команд и выводить пользователю результат их выполнения.

1) simple-telnetd запускает только разрешенные программы, перечисленные в файле /etc/simple-telnetd.conf (пример конфигурационного файла прилагается). Демон перечитывает конфигурационный файл по сигналу SIGHUP.
Примечание: в конфиг заносятся корректные команды с путем и без параметров. Пустые строки в конфиг лучше не заносить.

2) запускаемые пользовтелем команды могут иметь параметры командной строки, интерактивное взаимодействие не поддерживается.

3) обработка спец. символов не производится

4) через параметр командной строки демону может передаваться таймаут выполнения одной команды. Параметр задается в секундах, значение по умолчанию - 1 секунда.

5) работоспособность демона проверена на Linux, Mac, FreeBSD.

6) реализовано журналирование с уровнями важности DEBUG, INFO, и ERR от имени средства daemon.

Для запуска:

0) создать конфигурационный файл (пример ниже) в
/etc/simple-telnetd.conf

1) компиляция
c++ stelnetd.cpp

2) запуск без параметров:
./a.out

запуск с указанием таймаута в секундах:
./a.out 2

3) для подключения к демону используется telnet:
telnet localhost 6023

4) в сессии telnet можно вводить команды, например:
/bin/ls
/bin/ls -l /
/bin/pwd

5) для перезагрузки конфигурационного файла демону послается сигнал HUP:
killall -s SIGHUP a.out

6) для остановки демона послается сигнал TERM или KILL
killall -s SIGKILL a.out

Пример конфига:
Код

/bin/ls
/bin/pwd


Код демона:
Код

////////////////////////////
// Simple telnetd daemon 
//        by d2s1        
///////////////////////////

/*
* configuration file: /etc/simple-telnetd.conf
* SIGHUP - reload configuration file
* default socket: 0.0.0.0:6023
*/

#include <stdio.h>
#include <syslog.h>

#include <stdarg.h>

#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/resource.h>

#include <sys/socket.h>
#include <netinet/in.h>

#include <fstream>
#include <iostream>

#include <errno.h>
#include <sys/stat.h> // umask
#include <fcntl.h>  // open
#include <sys/ioctl.h>

#include <string.h>
#include <stdlib.h>

using namespace std;

//
// return values (error codes)
//
const int NO_ERROR = 0;
const int CONFIG_ERROR = 1;
const int SOCKET_ERROR = 2;
const int BIND_ERROR = 3;
const int ACCEPT_ERROR = 4;
const int PIPE_ERROR = 5;
const int WRITE_ERROR = 6;
const int READ_ERROR = 7;
const int SETSID_ERROR = 8;
const int CHDIR_ERROR = 9;
const int OPEN_ERROR = 10;

//
// globals
//
char valid_command_list[255][255];  // 255 commands
int list_length=0;  // actual number of commands
bool finalize=false;  // termination flag
int pio[2]; // pipes for std io: 0 - input, 1 - output & error
int timeout=1;  // command execution timeout (sec)
int sock,newsock;

// load conf file
static bool load_conf()
{
  char par[255];  // filename maximum length in HFS+ and ext3
  ifstream conf("/etc/simple-telnetd.conf");
  if (!conf)
  {
    syslog(LOG_ERR, "can not access config file: /etc/simple-telnetd.conf");
    return false;
  }
  int line=0;
  list_length=0;
  memset(valid_command_list, 0, 255 * 255);
  while((!conf.eof()) && (line < 255))
  {
    conf.getline(par, 255);
    size_t cmd_len = strcspn(par, "\r\n\t ");   // space symbols (delimiters)
    memcpy(valid_command_list[line], par, cmd_len);
    syslog(LOG_INFO, "loaded command %s", valid_command_list[line]);
    line++;
  }
  list_length=line;
  syslog(LOG_INFO, "configuration loaded.");
  return true;
}

// sighup handler - reload conf
static void sighup_handler(int signo)
{
  signal(SIGHUP, sighup_handler);
  close(sock);
  close(newsock);
  load_conf();
}

// sigterm handler - finalize
static void sigterm_handler(int signo)
{
   syslog(LOG_INFO, "terminated by SIGTERM.");
   finalize=true;
}

// server logic
static int server(int port)
{
     // constants
     const int BUF_SIZE = 1024;
     //locals
     int clen, readed_data_lenght;
     char buf[BUF_SIZE];
     struct sockaddr_in serv_addr, cli_addr;
     struct timeval tv;
     tv.tv_sec=240;
     tv.tv_usec=0;
     // socket connect
     sock = socket(AF_INET, SOCK_STREAM, 0);
     if (sock < 0) 
     {
       syslog(LOG_ERR, "can not create socket. %d %s", errno, strerror(errno));
       return SOCKET_ERROR;
     }
     setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
     memset((char *) &serv_addr, 0, sizeof(serv_addr));
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(port);
     if (bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
     {
       syslog(LOG_ERR, "can not bind socket. %d %s", errno, strerror(errno));
       close(sock);
       return BIND_ERROR;
     }
     while(!finalize)
     {
         listen(sock, 1);
         clen = sizeof(cli_addr);
         newsock = accept(sock, (struct sockaddr *) &cli_addr, (socklen_t *) &clen);
         if (newsock < 0) 
         {
           syslog(LOG_ERR, "can not accept connection. %d %s", errno, strerror(errno));
           close(sock);
           return ACCEPT_ERROR;
         }
         syslog(LOG_INFO, "connection open from %a", &cli_addr.sin_addr);
         //data read 
         while(!finalize)
         {
             memset(buf, 0, BUF_SIZE);
             readed_data_lenght = read(newsock, buf, BUF_SIZE-1);
             if(readed_data_lenght < 0)
             {
                if(errno == EWOULDBLOCK)
                {
                  syslog(LOG_ERR, "socket timeout.");
                }
                else
                {
                  syslog(LOG_ERR, "read error. %d %s", errno, strerror(errno));
                }
             }  
             else if (readed_data_lenght > 0)
             {
                size_t cmd_len = strcspn(buf, "\r\n\t ");   // space symbols (delimiters)
                char buffer[cmd_len+1];
                buffer[cmd_len] = 0;
                memcpy(buffer, buf, cmd_len);
                bool cmd_valid = false;
                for (int cmd=0; cmd < list_length; cmd++)
                {
//                  syslog(LOG_DEBUG, "compare:%svs%s", buffer, valid_command_list[cmd]);
                  if (strcmp(buffer, valid_command_list[cmd]) == 0)
                  {
                    cmd_valid = true;
                    break;
                  }
                }
                if (!cmd_valid)
                {
                    syslog(LOG_ERR, "invalid cmd: %s", buf);
                    // todo or not todo: send error message to user?
                }
                else
                {
                  syslog(LOG_INFO, "cmd valid: %s", buf);
                  char *arg[10];  // max possible arguments: 0..9 
                  int num = 1;
                  arg[0] = new char(strlen(buffer));
                  strcpy(arg[0], buffer);
                  char *p = strtok(buf, "\r\n\t ");
                  do 
                  {
                    p = strtok(NULL, "\r\n\t ");
                    if(p)
                    {
                      arg[num] = new char(strlen(p));
                      strcpy(arg[num],p);
                      syslog(LOG_DEBUG, "arg[%d]=%s", num, arg[num]);
                      num++;
                    }
                  } while(p);
                  char *ar[num+1];  // actual number of arguments
                  ar[num] = NULL;
                  for (int i = 0; i < num; i++)
                  {
                    ar[i] = new char(strlen(arg[i]));
                    strcpy(ar[i],arg[i]);
                    syslog(LOG_DEBUG, "ar[%d]=%s", i, ar[i]);
                  }
                  char answer[4096];
                  memset(answer, 0, 4096);
                  int pid = fork();
                  if (pid == 0)
                  {
                    // child
                   int execvretcode = 0;
                   if (num == 1)
                  {
                    syslog(LOG_DEBUG, "execv with NULL");
                    execvretcode = execv(buffer, NULL);
                  }
                  else
                  {
                       syslog(LOG_DEBUG, "execv %s with argv", buffer);
                       for (int ii=0; ii<num; ii++)
                       {
                         syslog(LOG_DEBUG, "ar[%d]=%s", ii, ar[ii]);
                       }
                       execvretcode = execv(buffer, ar);
                   }
                    if (execvretcode < 0)
                    {
                      syslog(LOG_ERR, "execv error. %d %s", errno, strerror(errno));
                      cerr<<strerror(errno)<<'\n';
                    }
                    else
                    {
                      // we are another process now
                    }
                  }
                  else
                  {
                    // parent
                    sleep(timeout);
                    int bytes = -1;
//                    cout<<"output\n";
                    syslog(LOG_DEBUG, "ioctl");
                    if (ioctl (pio[0], FIONREAD, &bytes) != 0)
                    {
                      syslog(LOG_ERR, "ioctl error. %d %s", errno, strerror(errno));
                      bytes = -1;
                    }
                    if (bytes > 0)
                    {
                      syslog(LOG_DEBUG, "read");
                      if (read(pio[0], answer, 4096) < 0)
                      {
                        syslog(LOG_ERR, "read error. %d %s", errno, strerror(errno));
                      }
                    }
                    syslog(LOG_DEBUG, "kill");
                    kill(pid, SIGKILL);
                  }
    
                  syslog(LOG_DEBUG, "send");
                  if (send(newsock, answer, strlen(answer), 0) != strlen(answer))
                  {
                    syslog(LOG_ERR, "send error: %d %s", errno, strerror(errno));
                  }
                }
    
             }
             else // readed_data_lenght == 0
             {
                syslog(LOG_INFO, "connection closed by remote host.");
                break;
             }
         }
     }    
     close(newsock);
     close(sock);
     return NO_ERROR;
}

// entry point
int main (int argc, char *argv[])
{
  int fd;
  int oldfd[3];
  struct rlimit flim;
  // open syslog using daemon facility
  openlog("Simple telnetd", LOG_PID | LOG_CONS, LOG_DAEMON);
  syslog(LOG_INFO, "starting...");
  // ignore terminal's signals
  if (getppid() != 1)
  {
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN); 
  }
  // handle SIGHUP todo use sigset_t
  signal(SIGHUP, sighup_handler);
  signal(SIGTERM, sigterm_handler); // for soft termination
  // check command line parameters
  if (argc > 1)
  {
    syslog(LOG_DEBUG, "argv[1]=%s", argv[1]);
    int to = atoi(argv[1]);
    if (to > 0)
    {
      syslog(LOG_INFO, "set timeout %d", to);
      timeout = to;
    }
  }
  // allocate pipes
  if (pipe(pio) < 0)
  {
    syslog(LOG_ERR, "can not allocate i/o pipes. %d %s", errno, strerror(errno));
    return PIPE_ERROR;
  }
  // switch std io to pipes
  oldfd[0]=dup(STDIN_FILENO);
  oldfd[1]=dup(STDOUT_FILENO);
  oldfd[2]=dup(STDERR_FILENO);
  dup2(pio[1], STDOUT_FILENO);
  dup2(pio[0], STDIN_FILENO);
  dup2(pio[1], STDERR_FILENO);
  // create new group and session leader
  if (fork() != 0)
  {
    return 0;
  }
  umask(0);
  if (setsid() < 0) // todo BSD - create group, open terminal and ioctl TIOCNOTTY
  {
    syslog(LOG_ERR, "setsid error: %d %s", errno, strerror(errno));
    return SETSID_ERROR;
  }
//  getrlimit(RLIMIT_NOFILE, &flim);
  //for (fd = 0; fd < flim.rlim_max; fd++)
  //{
    //if ((fd != STDIN_FILENO) && (fd != STDOUT_FILENO) && (fd != STDERR_FILENO))
    //close(fd);
  //  }

  // set current dir to root
  if (chdir("/") < 0)
  {
    syslog(LOG_ERR, "chdir error: %d %s", errno, strerror(errno));
    return CHDIR_ERROR;
  }
  if (!load_conf())
  {
    closelog();
    return CONFIG_ERROR;
  }
  syslog(LOG_INFO, "started.");

  int result_code = 0;
  while (!finalize)
  {
    result_code = server(6023);
  }

  // restore std io
  dup2(oldfd[0], STDIN_FILENO);
  dup2(oldfd[1], STDOUT_FILENO);
  dup2(oldfd[2], STDERR_FILENO);
  syslog(LOG_INFO, "stoped.");
  closelog();
  return result_code;
}


Примечания:
- демон однопоточный, одновременно с ним может работать не более 1 клиента;
- есть небольшие утечки памяти в функции server (3 new и 0 delete) smile ;
- закомментированный участок в функции main не работает под Mac'ом (потому и закоменчен) smile

Автор: GrayCardinal 8.3.2010, 16:57
Вроде не заказывали smile

Автор: azesmcar 8.3.2010, 17:06
Цитата(GrayCardinal @  8.3.2010,  16:57 Найти цитируемый пост)
Вроде не заказывали 

Поздно спохватились, придется платить smile 

Автор: d2s1 8.3.2010, 17:07
Вдруг кому-нибудь да пригодится smile

Автор: GrayCardinal 8.3.2010, 17:07
azesmcar, 
smile

Автор: Devourer 13.3.2010, 01:58
d2s1, уже ))) СПС ))))

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)