Эх сурвалжийг харах

Add Command System & Add FRPC Lifecycle Managements.

xcbosa-itx 2 жил өмнө
parent
commit
ae4bc4152a

+ 216 - 0
frp.hpp

@@ -0,0 +1,216 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#pragma once
+
+#include <string>
+#include "utils/utils.h"
+#include "webuiconf.h"
+#include "fs.hpp"
+#include <signal.h>
+#include <strstream>
+
+using namespace std;
+using namespace xc;
+using namespace xc::utils;
+
+namespace frp {
+
+    static set<int> profileUsingPorts(string profile) {
+        set<int> usingPorts;
+        INIFile ini(conf::getFrpcDir() + "/" + profile);
+        string value = ini.getMust("common")->get("webui_allowServerPorts");
+        auto v = split(value, ",");
+        int low(0), len(0);
+        if (v.size() == 2) {
+            try {
+                low = stoi(v[0]);
+                len = stoi(v[1]);
+            } catch (...) { }
+        }
+        for (int i = low; i < low + len; i++) {
+            usingPorts.insert(i);
+        }
+        return usingPorts;
+    }
+
+    static set<int> serverUsingPorts(string serverIp) {
+        set<int> usingPorts;
+        for (string file : fs::contentsOfDirectory(conf::getFrpcDir())) {
+            INIFile ini(conf::getFrpcDir() + "/" + file);
+            if (ini.getMust("common")->get("server_addr") == serverIp) {
+                auto profilePorts = profileUsingPorts(file);
+                for (auto port : profilePorts) {
+                    usingPorts.insert(port);
+                }
+            }
+        }
+        return usingPorts;
+    }
+
+    static void addProfile(string name, string ip, string port, string token) {
+        INI ini;
+        auto frpCommon = ini.getMust("common");
+        frpCommon->set("server_addr", ip);
+        frpCommon->set("server_port", port);
+        frpCommon->set("token", token);
+        frpCommon->set("tls_enable", "true");
+        auto alreadyUsingPorts = serverUsingPorts(ip);
+        int low = 0, len = conf::allowPortCountPerProfile;
+        for (int i = 10000; i < 60000; i += len) {
+            bool badPage = false;
+            for (int j = i; j < i + len; j++) {
+                if (alreadyUsingPorts.count(j)) {
+                    badPage = true;
+                    break;
+                }
+            }
+            if (!badPage) {
+                low = i;
+                break;
+            }
+        }
+        if (low == 0) {
+            len = 0;
+        }
+        ostringstream oss;
+        oss << low;
+        oss << ",";
+        oss << len;
+        frpCommon->set("webui_allowServerPorts", oss.str());
+        saveTextFile(conf::getFrpcDir() + "/" + name, ini.getINIString());
+    }
+
+    class FrpProcessWrapper {
+    public:
+        FrpProcessWrapper(string fileName, string filePath) {
+            this->fileName = fileName;
+            this->filePath = filePath;
+            this->updated = false;
+        }
+
+        void startAndKeepRunning() {
+            cout << "[FrpProcessWrapper] [" << fileName << "] Start" << endl;
+            this->doKill();
+            this->doStart();
+        }
+
+        void update() {
+            if (getRunningPid() == 0) {
+                cout << "[FrpProcessWrapper] [" << fileName << "] Exit unexpectedly, restarting..." << endl;
+                this->doStart();
+            }
+        }
+
+        int getRunningPid() {
+            ::FILE *psStdoutFd = popen("ps -ef", "r");
+            ostringstream oss;
+            while (true) {
+                int ch = ::fgetc(psStdoutFd);
+                if (ch == -1) break;
+                oss << (char) ch;
+            }
+            ::pclose(psStdoutFd);
+            string str = oss.str();
+            auto lines = split(str, "\n");
+            for (auto line : lines) {
+                if (line.find("frpc -c") != line.npos && line.find(this->filePath) != line.npos) {
+                    strstream ss;
+                    ss << line;
+                    string owner;
+                    int pid;
+                    ss >> owner;
+                    ss >> pid;
+                    return pid;
+                }
+            }
+            return 0;
+        }
+
+        void stop() {
+            cout << "[FrpProcessWrapper] [" << fileName << "] Stop" << endl;
+            this->doKill();
+        }
+
+        void reloadConfig() {
+            this->stop();
+            this->startAndKeepRunning();
+        }
+
+        bool updated;
+        string fileName;
+        string filePath;
+    private:
+        void doStart() {
+            ostringstream oss;
+            oss << "frpc -c " << this->filePath << " &";
+            string launchCmd = oss.str();
+            ::system(launchCmd.c_str());
+        }
+
+        void doKill() {
+            int pid = getRunningPid();
+            if (pid > 0) {
+                kill(pid, SIGKILL);
+            }
+        }
+    };
+
+    set<string> reloadConfigForFilePathRequests;
+    mutex reloadConfigForFilePathRequestsLocker;
+
+    static void reloadProfileFilePath(string filePath) {
+        reloadConfigForFilePathRequestsLocker.lock();
+        reloadConfigForFilePathRequests.insert(filePath);
+        reloadConfigForFilePathRequestsLocker.unlock();
+    }
+
+    static void frpDaemon() {
+        char readBuff[1024];
+        vector<FrpProcessWrapper *> frpProcesses;
+        while (true) {
+            for (auto it : frpProcesses) {
+                it->updated = false;
+            }
+            for (string file : fs::contentsOfDirectory(conf::getFrpcDir())) {
+                string filePath = conf::getFrpcDir() + "/" + file;
+                bool updated = false;
+                for (auto process : frpProcesses) {
+                    if (process->filePath == filePath) {
+                        process->update();
+                        process->updated = true;
+                        updated = true;
+                        break;
+                    }
+                }
+                if (!updated) {
+                    auto newProcess = new FrpProcessWrapper(file, filePath);
+                    newProcess->startAndKeepRunning();
+                    newProcess->updated = true;
+                    frpProcesses.push_back(newProcess);
+                }
+            }
+            for (auto it = frpProcesses.begin(); it != frpProcesses.end(); ) {
+                FrpProcessWrapper *proc = *it;
+                if (!proc->updated) {
+                    proc->stop();
+                    delete proc;
+                    it = frpProcesses.erase(it);
+                } else {
+                    reloadConfigForFilePathRequestsLocker.lock();
+                    if (reloadConfigForFilePathRequests.count(proc->filePath)) {
+                        reloadConfigForFilePathRequests.erase(proc->filePath);
+                        reloadConfigForFilePathRequestsLocker.unlock();
+                        proc->reloadConfig();
+                    } else {
+                        reloadConfigForFilePathRequestsLocker.unlock();
+                    }
+                    it++;
+                }
+            }
+            usleep(1000 * 1000);
+        }
+    }
+
+}

+ 23 - 0
fs.hpp

@@ -2,6 +2,7 @@
 
 #include <sys/stat.h>
 #include <string>
+#include <dirent.h>
 #include "utils/utils.h"
 
 using namespace std;
@@ -26,6 +27,10 @@ namespace fs {
         return false;
     }
 
+    inline bool deleteFile(string filePath) {
+        return ::remove(filePath.c_str()) == 0;
+    }
+
     inline bool existsDirectory(string filePath) {
         struct stat buffer;
         if (stat(filePath.c_str(), &buffer) == 0) {
@@ -34,4 +39,22 @@ namespace fs {
         return false;
     }
 
+    inline vector<string> contentsOfDirectory(string filePath) {
+        DIR *pDir;
+        struct dirent* ptr;
+        vector<string> ret;
+        if (!(pDir = opendir(filePath.c_str()))){
+            return ret;
+        }
+        while ((ptr = readdir(pDir)) != 0) {
+            if (::strlen(ptr->d_name) > 0) {
+                if (strcmp(ptr->d_name, ".") != 0 && strcmp(ptr->d_name, "..") != 0 && ptr->d_name[0] != '.') {
+                    ret.push_back(ptr->d_name);
+                }
+            }
+        }
+        closedir(pDir);
+        return ret;
+    }
+
 }

+ 46 - 2
main.cpp

@@ -4,16 +4,60 @@
 #include "httpserver/http-server.h"
 #include "utils/utils.h"
 #include "processor/processor.h"
+#include "fs.hpp"
+#include "thirdparty/sha256.hpp"
+#include "frp.hpp"
 
 using namespace xc;
 using namespace xc::httpserver;
 using namespace xc::processor;
 
-int main() {
+const static strcmd reg("reg", "reg <User> <Pwd>", "注册用户", 2, [] (auto args) {
+    INI ini;
+    ini.getMust("info")->set("password", sha256(args[1] + conf::userPasswordSalt));
+    saveTextFile(conf::getUserDataDir() + "/" + args[0], ini.getINIString());
+    cout << "成功创建用户 " << args[0] << endl;
+});
+
+const static strcmd createFrp("frp", "frp <Name> <Server-Addr> <Server-Port> <Token>", "创建Frpc配置文件", 4, [] (auto args) {
+    frp::addProfile(args[0], args[1], args[2], args[3]);
+    cout << "成功创建Frpc配置文件 " << args[0] << endl;
+});
+
+int main(int argc, char **argv) {
     std::cout << "Hello, World!" << std::endl;
+
+    conf::getRootDir();
+    conf::getUserDataDir();
+    conf::getFrpcDir();
+
+    ostringstream oss;
+    oss << "ps -ef | grep " << argv[0] << " | grep -v grep";
+    ::FILE *psStdoutFd = popen(oss.str().c_str(), "r");
+    ostringstream w_oss;
+    while (true) {
+        int ch = ::fgetc(psStdoutFd);
+        if (ch == -1) break;
+        w_oss << (char) ch;
+    }
+    ::pclose(psStdoutFd);
+    if (utils::split(w_oss.str(), "\n").size() > 1) {
+        cerr << "FrpcWebUI只能运行一个进程,您已启动了多个。" << endl;
+        return -1;
+    }
+
+    if (fs::contentsOfDirectory(conf::getUserDataDir()).empty()) {
+        cout << "看起来您没有设置任何一个账号,您可以输入 reg <账号> <密码> 来注册。" << endl;
+    }
+
     HTTPServer server(8192);
     thread([&server] { server.serverLoop(); }).detach();
+
     RequestProcessWorker worker;
     thread([&worker] { worker.workerLoop(); }).detach();
-    while (true) usleep(1000);
+
+    thread([] { frp::frpDaemon(); }).detach();
+
+    CommandLineWorker cmdLine;
+    cmdLine.workerLoop();
 }

+ 1 - 8
processor/templates/ViewTemplatePrototypes.cpp

@@ -19,13 +19,6 @@ namespace xc {
                 return src;
             }
 
-            string fixStringFormat(string strToFix) {
-                replace_all(strToFix, "\"", "\\\"");
-                replace_all(strToFix, "\r", "\\\r");
-                replace_all(strToFix, "\n", "\\\n");
-                return strToFix;
-            }
-
 //            string generateInner(ViewTemplatePrototype inner) {
 //                ostringstream oss;
 //                inner.generateHTML(oss);
@@ -59,7 +52,7 @@ namespace xc {
                         for (auto it: this->properties) {
                             writeTo << it.first;
                             writeTo << "=\"";
-                            writeTo << fixStringFormat(it.second);
+                            writeTo << fixStringTransfer(it.second);
                             writeTo << "\" ";
                         }
 

+ 1 - 2
thirdparty/sha256.hpp

@@ -19,9 +19,8 @@ string sha256(string str) __attribute__((weak)) {
     const char *c_str = str.c_str();
     __sha256((unsigned char *)c_str, ::strlen(c_str), buff);
     ostringstream oss;
-    oss << hex;
     for (int i = 0; i < 32; i++) {
-        oss << (int)(buff[i]);
+        oss << hex << setiosflags(ios::right) << setw(2) << setfill('0') << (int)(buff[i]);
     }
     return oss.str();
 }

+ 0 - 23
user.hpp

@@ -42,29 +42,6 @@ namespace user {
         return "loginFailed";
     }
 
-    static vector<string> split(const string& str, const string& delim) {
-        vector<string> res;
-        if ("" == str) return res;
-        char * strs = new char[str.length() + 1];
-        strcpy(strs, str.c_str());
-        char * d = new char[delim.length() + 1];
-        strcpy(d, delim.c_str());
-        char *p = strtok(strs, d);
-        while(p) {
-            string s = p;
-            res.push_back(s);
-            p = strtok(NULL, d);
-        }
-        return res;
-    }
-
-    static std::string& trim(std::string &s) {
-        if (s.empty()) { return s; }
-        s.erase(0,s.find_first_not_of(" "));
-        s.erase(s.find_last_not_of(" ") + 1);
-        return s;
-    }
-
     inline string getTokenUserName(string token) __attribute__((weak)) {
         auto list = split(token, "/");
         if (list.size() != 3) {

+ 79 - 0
utils/CommandLine.cpp

@@ -0,0 +1,79 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#include "CommandLine.h"
+#include "strop.h"
+#include <unistd.h>
+
+namespace xc {
+    namespace utils {
+
+        static CommandLineCommand *registeredCommands[1024];
+        static int registeredCommandsId(0);
+
+        CommandLineCommand::CommandLineCommand(string name, string usage, string message) {
+            this->commandName = name;
+            this->commandUsage = usage;
+            this->message = message;
+            assert(registeredCommandsId < 1024);
+            registeredCommands[registeredCommandsId++] = this;
+            cout << "[CommandLineCommand] Registered " << commandName << endl;
+        }
+
+        CommandLineStringCommand::CommandLineStringCommand(string commandName, string commandUsage, string message, int argCnt, function<void (vector<string>)> handler):
+                CommandLineCommand(commandName, commandUsage, message) {
+            this->argCnt = argCnt;
+            this->handler = handler;
+        }
+
+        void CommandLineStringCommand::evaluate(string userInputLine) const {
+            auto list = split(userInputLine, " ");
+            vector<string> result;
+            for (int i = 1; i < list.size(); i++) {
+                string copied = list[i];
+                trim(copied);
+                result.push_back(copied);
+            }
+            if (result.size() != this->argCnt) {
+                cerr << "Usage: " << this->commandUsage << endl;
+            } else {
+                this->handler(result);
+            }
+        }
+
+        void CommandLineWorker::workerLoop() {
+            char cinReadBuff[1024];
+            while (true) {
+                bzero(cinReadBuff, sizeof(cinReadBuff));
+                cin.getline(cinReadBuff, sizeof(cinReadBuff));
+                string str(cinReadBuff);
+                if (str.empty()) continue;
+                auto list = split(str, " ");
+                string titleUppercase = uppercase(list[0]);
+                if (titleUppercase == "HELP") {
+                    cout << "Frp-WebUI Command List" << endl;
+                    for (int i = 0; i < registeredCommandsId; i++) {
+                        cout << registeredCommands[i]->message << " : " << registeredCommands[i]->commandUsage << endl;
+                    }
+                } else {
+                    bool founded = false;
+                    for (int i = 0; i < registeredCommandsId; i++) {
+                        auto command = registeredCommands[i];
+                        if (uppercase(command->commandName) == titleUppercase) {
+                            command->evaluate(str);
+                            founded = true;
+                            break;
+                        }
+                    }
+                    if (!founded) {
+                        cerr << "Command " << list[0] << " not founded, type help to view help." << endl;
+                    }
+                }
+                cin.clear();
+                usleep(1000 * 10);
+            }
+        }
+
+    } // xc
+} // utils

+ 41 - 0
utils/CommandLine.h

@@ -0,0 +1,41 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#pragma once
+#include "utils-private.h"
+
+namespace xc {
+    namespace utils {
+
+        class CommandLineWorker;
+
+        class CommandLineCommand {
+            friend CommandLineWorker;
+        public:
+            CommandLineCommand(string name, string usage, string message);
+            virtual void evaluate(string userInputLine) const = 0;
+        protected:
+            string commandName;
+            string commandUsage;
+            string message;
+        };
+
+        class CommandLineStringCommand: public CommandLineCommand{
+        public:
+            CommandLineStringCommand(string commandName, string commandUsage, string message, int argCnt, function<void (vector<string>)> handler);
+            void evaluate(string userInputLine) const override;
+        private:
+            int argCnt;
+            function<void (vector<string>)> handler;
+        };
+
+        typedef CommandLineStringCommand strcmd;
+
+        class CommandLineWorker {
+        public:
+            void workerLoop();
+        };
+
+    } // xc
+} // utils

+ 1 - 33
utils/INI.cpp

@@ -4,43 +4,11 @@
 
 #include "INI.h"
 #include "utils.h"
+#include "strop.h"
 
 namespace xc {
     namespace utils {
 
-        static vector<string> split(const string& str, const string& delim) {
-            vector<string> res;
-            if ("" == str) return res;
-            char * strs = new char[str.length() + 1];
-            strcpy(strs, str.c_str());
-            char * d = new char[delim.length() + 1];
-            strcpy(d, delim.c_str());
-            char *p = strtok(strs, d);
-            while(p) {
-                string s = p;
-                res.push_back(s);
-                p = strtok(NULL, d);
-            }
-            return res;
-        }
-
-        static string& replace_all(string& src, const string& old_value, const string& new_value) {
-            for (string::size_type pos(0); pos != string::npos; pos += new_value.length()) {
-                if ((pos = src.find(old_value, pos)) != string::npos) {
-                    src.replace(pos, old_value.length(), new_value);
-                }
-                else break;
-            }
-            return src;
-        }
-
-        static std::string& trim(std::string &s) {
-            if (s.empty()) { return s; }
-            s.erase(0,s.find_first_not_of(" "));
-            s.erase(s.find_last_not_of(" ") + 1);
-            return s;
-        }
-
         INISection::INISection(): title(), data() { }
 
         INISection::INISection(string title, map<string, string> data): title(title), data(data) { }

+ 1 - 23
utils/RequestData.cpp

@@ -4,6 +4,7 @@
 
 #include "RequestData.h"
 #include "regex"
+#include "strop.h"
 
 using namespace std;
 
@@ -103,29 +104,6 @@ namespace xc {
             return res;
         }
 
-        vector<string> split(const string& str, const string& delim) {
-            vector<string> res;
-            if ("" == str) return res;
-            char * strs = new char[str.length() + 1];
-            strcpy(strs, str.c_str());
-            char * d = new char[delim.length() + 1];
-            strcpy(d, delim.c_str());
-            char *p = strtok(strs, d);
-            while(p) {
-                string s = p;
-                res.push_back(s);
-                p = strtok(NULL, d);
-            }
-            return res;
-        }
-
-        std::string& trim(std::string &s) {
-            if (s.empty()) { return s; }
-            s.erase(0,s.find_first_not_of(" "));
-            s.erase(s.find_last_not_of(" ") + 1);
-            return s;
-        }
-
         string RequestData::getCookie(string key) {
             string cookies = this->getHeader("Cookie");
             if (cookies.empty()) { return ""; }

+ 62 - 0
utils/strop.cpp

@@ -0,0 +1,62 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#include "strop.h"
+
+namespace xc {
+    namespace utils {
+
+        vector<string> split(const string& str, const string& delim) {
+            vector<string> res;
+            if ("" == str) return res;
+            char *strs = new char[str.length() + 1];
+            strcpy(strs, str.c_str());
+            char *d = new char[delim.length() + 1];
+            strcpy(d, delim.c_str());
+            char *p = strtok(strs, d);
+            while (p) {
+                string s = p;
+                res.push_back(s);
+                p = strtok(NULL, d);
+            }
+            delete[] strs;
+            delete[] d;
+            return res;
+        }
+
+        string replace_all(string& src, const string& old_value, const string& new_value) {
+            for (string::size_type pos(0); pos != string::npos; pos += new_value.length()) {
+                if ((pos = src.find(old_value, pos)) != string::npos) {
+                    src.replace(pos, old_value.length(), new_value);
+                }
+                else break;
+            }
+            return src;
+        }
+
+        string fixStringTransfer(string& src) {
+            string modify = src;
+            replace_all(modify, "\"", "\\\"");
+            replace_all(modify, "\r", "\\\r");
+            replace_all(modify, "\n", "\\\n");
+            return modify;
+        }
+
+        string& trim(string &s) {
+            if (s.empty()) { return s; }
+            s.erase(0,s.find_first_not_of(" "));
+            s.erase(s.find_last_not_of(" ") + 1);
+            return s;
+        }
+
+        string uppercase(string s) {
+            ostringstream oss;
+            for (char ch : s) {
+                oss << (char)::toupper(ch);
+            }
+            return oss.str();
+        }
+
+    } // xc
+} // utils

+ 22 - 0
utils/strop.h

@@ -0,0 +1,22 @@
+//
+// Created by xcbosa on 2023/1/31.
+//
+
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+
+namespace xc {
+    namespace utils {
+
+        vector<string> split(const string& str, const string& delim);
+        string replace_all(string& src, const string& old_value, const string& new_value);
+        string fixStringTransfer(string& src);
+        string& trim(string &s);
+        string uppercase(string s);
+
+    } // xc
+} // utils

+ 2 - 0
utils/utils.h

@@ -11,6 +11,8 @@
 #include "BinaryResponseData.h"
 #include "INI.h"
 #include "RedirectResponse.h"
+#include "CommandLine.h"
+#include "strop.h"
 
 using namespace std;
 

+ 19 - 6
webuiconf.h

@@ -22,26 +22,39 @@ namespace xc::conf {
     const bool enableStaticAssetsController = false;
 
     const string title("Frp-WebUI-XCBOSA");
+    const int allowPortCountPerProfile(10);
 
     const string userPasswordSalt("eDGJ&v,.W0U(66.lVQFsKfWb*bm*M+Lj");
     const string userJWTSecret("r)xB=P-6A4dpqXLk%03=f+*8TlXDM@%r");
-    const string userDataDir = "/etc/frpcwebui/users";
+    const string rootDir = "/etc/frpcwebui";
     const int userTokenExpireSeconds = 60 * 60 * 24;
 
-    inline string getUserDataDir() {
+    inline string getDirAndMakesureExists(string dirPath) {
         struct stat buffer;
-        if (stat(userDataDir.c_str(), &buffer) == 0) {
+        if (stat(dirPath.c_str(), &buffer) == 0) {
             assert(!S_ISREG(buffer.st_mode));
-            return userDataDir;
+            return dirPath;
         }
         mode_t old_mask = umask(0);
-        int n_ret = mkdir(userDataDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
+        int n_ret = mkdir(dirPath.c_str(), S_IRWXU | S_IRGRP | S_IROTH);
         umask(old_mask);
         if (n_ret != 0) {
             ::perror("");
             assert(n_ret == 0);
         }
-        return userDataDir;
+        return dirPath;
+    }
+
+    inline string getRootDir() {
+        return getDirAndMakesureExists(rootDir);
+    }
+
+    inline string getUserDataDir() {
+        return getDirAndMakesureExists(getRootDir() + "/users");
+    }
+
+    inline string getFrpcDir() {
+        return getDirAndMakesureExists(getRootDir() + "/frpc");
     }
 
     const map<string, string> fileExtensionToMimeTypes = {