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. 

Last updated

Was this helpful?