Dzięki za pomoc.
Ty chcesz zrobić coś podobnego, a konkretniej, ponieważ piszesz "coś jak telnet", to chcesz zrobić:
-
fork()
-
w procesie dziecku:
- dup2 socketu który masz z połączeniem sieciowym na fd 0, 1, 2 (czyli stdin, stdout i stderr) - to spowoduje przekierowanie standardowych wejść/wyjść na socket sieciowy.
- execve /bin/bash (chociaż prościej execvp - wtedy nie musisz re-definiować zmiennych środowiskowych) - dzięki temu uruchomiony zostanie shell, który będzie sterowany przez połączenie sieciowe
Został mi jeden problem:
a) odpalam basha w ten sposób:
int error_val;
char *const args[] = { (char *)"bash",NULL };
error_val=execvp("bash", args);
wówczas jak w bashu wpisze:
;
to dostaje:
bash: line 1: syntax error near unexpected token `;'
bash: line 1: `;'
i połączenie jest zamykane - w serwerze wypisuje mi
4150 exited with return code 2
gdzie 4150 to pid basha.
Chciałbym żeby po wpisaniu ; zabawa się nie kończyła - żebym dostał wiadomość, że jest error ale żebym dalej mógł rozmawiać z bashem (ale nie w taki sposób, że muszę odpalić kolejnego basha).
b)odpalam basha w ten sposób (interaktywnie):
int error_val;
char *const args[] = { (char*)"bash",(char*)"-i", NULL };//"-i" interaktywna z promptem
error_val=execvp("bash", args);
i tu nie mam problemu, że mi zamyka połączenie jak wpisze:
;
Jest za to problem, którego nie potrafię wytłumaczyć:
mój serwer może obsłużyć tylko 1 połączenie! (w trybie nie-interaktywnym nie ma z tym problemu i obsługuje wiele)
konsola 1)
nc localhost 1234
output serwera:
new connection: 127.0.0.1
konsola 2)
nc localhost 1234
output serwera
new connection: 127.0.0.1
new connection: 127.0.0.1
[1]+ Stopped ./server.o -i
W sumie nawet nie wiem jak testować co jest nie tak, bo mam w procesie ojcu wait, odczytuje errno ale nie wypisuje mi nawet tego errno.
Przeczytałem https://www.gnu.org/software/bash/manual/bashref.html#Interactive-Shell-Behavior ale tam nie widze, dlaczego miałby być jakiś problem po zmianie na tryb interaktywny.
Problem 2 (mało istotny)) z bash -i: odpalam
./server.o -i
i jak ktoś się do mnie połączy to sigint w konsoli z serwerem nie zabija procesu (ctrl+c) (dopiero jak się rozłącze z serwerem to jestem w stanie zakończyć serwer z tej konsoli, na której jest serwer)
Server starting on port 1234 using bash interactive mode.
new connection: 127.0.0.1
^C^C^C //3x ctrl +C
podczas gdy w bashu nieinteraktywnym bez problemu zamykam wszystkie połączenia i kończę program.
Jestem to w stanie zabić z innej konsoli kill -INT PID_server.io //sprawdzony przez ps axf | grep server.o
Mój pełny kod
Makefile:
all:
g++ -std=c++0x -Wall -Werror -pthread -o server.o server.cpp
server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <pthread.h>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <set>
#include <vector>
#include <sys/wait.h>
bool interactive;
struct cln {
int cfd;
struct sockaddr_in caddr;
};
std::set<int> bashChildrenPIDs;
void SIGINT_handler(int signo)
//przy ctrl+c albo kill -INT PID przerywa polaczenia i sprzata bashe
{
if (signo == SIGINT){
printf("received SIGINT - shutting down telnet server\n");
for(auto bashProcessPIDToKill: bashChildrenPIDs){
printf("killing bash PID %d\n",bashProcessPIDToKill);
kill(bashProcessPIDToKill,SIGINT);
}
exit(0);
}
}
void* cthread(void* arg){
struct cln* c=(struct cln*) arg;
printf("new connection: %s\n",inet_ntoa((struct in_addr)c->caddr.sin_addr));
int fork_pid=fork();
if(fork_pid==-1){
perror("failed to fork");
}
//child
else if(!fork_pid){
dup2(c->cfd,0 );
dup2(c->cfd,1 );
dup2(c->cfd,2 );
int error_val;
if(interactive){
char *const args[] = { (char*)"bash",(char*)"-i", NULL };//"-i" interaktywna z promptem
error_val=execvp("bash", args);
}
else{
char *const args[] = { (char *)"bash",NULL };
error_val=execvp("bash", args);
}
if(error_val){
exit(error_val);
}
}
//parent
else if (fork_pid>0){
bashChildrenPIDs.insert(fork_pid);
//wiekszosc z tego ifa wzialem z http://stackoverflow.com/a/747819
int status;
if (wait(&status) == -1)
{
perror("wait()");
}
else
{
bashChildrenPIDs.erase(fork_pid);
/* did the child terminate normally? */
if(WIFEXITED(status))
{
printf("%ld exited with return code %d\n",
(long)fork_pid, WEXITSTATUS(status));
}
/* was the child terminated by a signal? */
else if (WIFSIGNALED(status))
{
printf("%ld terminated because it didn't catch signal number %d\n",
(long)fork_pid, WTERMSIG(status));
}
close(c->cfd);
free(c);
}
}
return NULL;
}
int main(int argc, char** argv){
//./server.o opcjonalneParametry
//-p numerWybranegoPrzezCiebiePortu (domyslnie jak uzytkownik nie poda to port 1234)
//-i (czy ma byc konsola interaktywna)
std::vector<int> signalsForSigHandlerFunction={SIGINT,SIGTERM};
for(auto signalToRegister: signalsForSigHandlerFunction)
if (signal(signalToRegister, SIGINT_handler) == SIG_ERR)
printf("\ncan't catch %d\n",signalToRegister);
int portNum=1234;
for(int param_num=0;param_num<argc;++param_num){
if(strcmp(argv[param_num],"-i")==0)
interactive=true;
if(strcmp(argv[param_num],"-p")==0)
portNum=atoi(argv[param_num+1]);
}
std::cout<<"Server starting on port "<<portNum<<" using bash ";
if(!interactive)
std::cout<<"non-";
std::cout<<"interactive mode."<<std::endl;
pthread_t tid;
int fd=socket(PF_INET,SOCK_STREAM,0);
int on=1;
struct sockaddr_in sa;//server addr
sa.sin_family=PF_INET;
sa.sin_port=htons(portNum);
sa.sin_addr.s_addr=INADDR_ANY;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on));//zeby zwalnial port po zabiciu procesu
bind(fd,(struct sockaddr*)&sa,sizeof(sa));
listen(fd,15);
while(1){
struct cln* c=(cln*)malloc(sizeof(struct cln));
int wielkosc_sockaddr=sizeof(c->caddr);
c->cfd=accept(fd,(struct sockaddr*)&c->caddr,(socklen_t*)&wielkosc_sockaddr);
pthread_create(&tid,NULL,cthread,c);
pthread_detach(tid);
}
close(fd);
return 0;
}
czemu nie SSH?
Taki sobie wybrałem temat na sieciach.