Remote Commander
ตัวอย่างโปรแกรมประยุกต์เพื่อใช้ในการส่งชุดคำสั่งระยะไกล เพื่อให้ไปประมวลผลคำสั่งที่เครื่องปลายทาง (server.c
) แล้วส่งผลลัพธ์กลับมายังเครื่องที่สั่งการ (client.c
) โดยใช้เทคนิคการสื่อสารด้วย socket ดังตัวอย่างข้างล่างนี้
โปรแกรม server.c
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 8080
// Function to execute shell commands and return results.
void execute_command(char* command, char* result, int resultBufferSize) {
FILE* pipe = popen(command, "r");
if (!pipe) {
strcpy(result, "popen() failed!");
return;
}
char buffer[128];
int totalBytesRead = 0;
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
int bytesRead = strlen(buffer);
// Make sure we don't exceed the buffer size
if (totalBytesRead + bytesRead >= resultBufferSize) {
break;
}
strcat(result, buffer); // Append the new data to the total result
totalBytesRead += bytesRead;
}
if (pclose(pipe) != 0) {
// Handle errors reported by pclose(),
// perhaps appending a message to the result
}
}
int main(int argc, char const *argv[]) {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char result[1024];
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to the PORT 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Binding socket to the PORT 8080
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// Display the server's IP and listening port
char host[256];
char *IP;
struct hostent *host_entry;
gethostname(host, sizeof(host));
host_entry = gethostbyname(host);
IP = inet_ntoa(*((struct in_addr*) host_entry->h_addr_list[0]));
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server is running at IP: %s on Port: %d\n", IP, PORT);
// Main loop to keep server running and accepting new connections
while (1) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("Connection established...\n");
// Communication loop with the connected client
while (1) {
memset(buffer, 0, BUFFER_SIZE); // Clear the buffer
int read_size = read(new_socket, buffer, BUFFER_SIZE);
if (read_size > 0) {
printf("Received command: %s\n", buffer);
// Special case: close connection if client sends "exit"
if (strcmp(buffer, "exit") == 0) {
printf("Closing connection...\n");
close(new_socket);
break; // Break out of the inner loop to start accepting new connections
}
// Execute the command
execute_command(buffer, result, sizeof(result));
// printf("Command result: %s\n", result);
send(new_socket, result, strlen(result), 0);
printf("Result sent back to client\n");
memset(result, 0, BUFFER_SIZE); // Clear the result buffer
} else {
printf("Client disconnected or read error...\n");
close(new_socket);
break; // Break out of the inner loop to start accepting new connections
}
}
}
return 0;
}
เพื่อให้โปรแกรมสั่งการ (client.c
) ทำตัวเองเหมือน Shell terminal ในการรันคำสั่งจากผู้ใช้ โดยใช้ตามมาตราฐาน VT100/ANSI Terminal จึงต้องมีการติดตั้ง library ที่ชื่อว่า libreadline-dev ก่อน เพื่อเรียกใช้ในโปรแกรม client.c ได้ดังนี้
$ sudo apt-get install libreadline-dev
// client.c
/* Prerequisite:
Install the required library named "Libreadline-dev"
sudo apt-get install libreadline-dev
Compile:
gcc -Wall -o myshell myshell.c -lreadline
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <readline/readline.h>
#include <readline/history.h>
#define BUFFER_SIZE 1024
#define PURPLE "\033[95m"
#define CYAN "\033[96m"
#define DARKCYAN "\033[36m"
#define BLUE "\033[94m"
#define GREEN "\033[92m"
#define YELLOW "\033[93m"
#define RED "\033[91m"
#define BOLD "\033[1m"
#define UNDERLINE "\033[4m"
#define END "\033[0m"
#define SHELL_PROMPT "\033[91m\033[1mShell>\033[0m"
// Function to execute shell commands
void execute_command(int socket, const char *cmd) {
// system(cmd); // Execute the command using system()
send(socket, cmd, strlen(cmd), 0);
}
// Function to initialize tab completion process
char** command_completion(const char* text, int start, int end) {
rl_attempted_completion_over = 1; // Disable default filename completion
return NULL; // Here, one would insert the logic for command-specific auto-completion
}
// Custom function to implement auto-complete of commands
void initialize_readline() {
rl_attempted_completion_function = command_completion;
}
void display_help() {
printf("\nBuilt-in commands:\n");
printf(" help : Display this help text\n");
printf(" exit : Exit the shell\n");
printf(" ls : List directory contents\n");
printf(" pwd : Print the current working directory\n");
printf(" date : Show the current date and time\n");
printf(" whoami : Print the user name associated with the current effective user ID\n");
// Add any additional commands here with a brief description
printf("\nUse the man command for information on other programs.\n\n");
}
int main(int argc, char const *argv[]) {
char* cmd;
struct sockaddr_in address;
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
if(argc != 3) {
printf("\n Usage: %s <ip of server> <port> \n", argv[0]);
return -1;
}
char *ipAddress = argv[1];
int port = atoi(argv[2]);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
// Convert IPv4 and IPv6 addresses from text to binary form
if(inet_pton(AF_INET, ipAddress, &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
initialize_readline(); // Set up the Readline library for our use
// List of default commands
char* default_commands[] = {
"ls",
"pwd",
"echo Hello, World!",
"date",
"whoami",
NULL // Array terminator
};
// Add default commands to history
for (int i = 0; default_commands[i] != NULL; i++) {
add_history(default_commands[i]);
}
while (1) {
cmd = readline(SHELL_PROMPT); // Display prompt and read input (also allows editing)
if (!cmd) {
break; // Exit on Ctrl+D or if no more input
}
// Skip if the command is empty
if (strcmp(cmd, "") == 0) {
free(cmd);
continue;
}
// If the command is "help", display the built-in command usage
if (strcmp(cmd, "help") == 0) {
display_help();
free(cmd);
continue; // Skip the rest of the loop for this iteration
}
// If the command is "exit", to exit program
if (strcmp(cmd, "exit") == 0) {
free(cmd);
close (sock); // close the connection
return 1; // exit program
}
// Add command to history
add_history(cmd);
// Execute the command
execute_command(sock, cmd);
// Wait for the server response
int valread = read(sock, buffer, 1024);
printf("%s %s %s\n", PURPLE, buffer, END); // Print server response
// Clean up
free(cmd);
memset(buffer, 0, sizeof(buffer)); // Clear the buffer after each response.
}
return 0;
}
ทำการคอมไพล์โปรแกรมทั้งสอง โดยสร้างหน้าต่าง terminal 2 หน้าต่าง ดังนี้
$ gcc -o server server.c -Wall
$ ./server ─╯
Server is running at IP: 10.211.55.3 on Port: 8080
Connection established...
Result sent back to client.
$ gcc -Wall -o client client.c -lreadline
$ ./client 10.211.55.3 8080 ─╯
shell> ls <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]:
client
client.c
new_server
new_server.c
other.c
server
server.c
shell> ifconfig <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]:
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:88:82:fe:7b txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
enp0s5: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.211.55.3 netmask 255.255.255.0 broadcast 10.211.55.255
inet6 fe80::c7de:82d6:f3cc:96a0 prefixlen 64 scopeid 0x20<link>
inet6 fd5c:edc7:dfd5:6910:6ea3:1b97:d393:2793 prefixlen 64 scopeid 0x0<global>
inet6 fd5c:edc7:dfd5:6910:1956:adea:5a7b:c494 prefixlen 64 scopeid 0x0<global>
ether 00:1c:42:8a:01:82 txqueuelen 1000 (Ethernet)
RX packets 1908378 bytes 2334364363 (2.3 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 501740 bytes 57225273 (57.2 MB)
TX errors 0 dropped 0 overruns
Last updated