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

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University