From 7f31d622608cfa2c4724bf15934c6addf88e8034 Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Tue, 10 Jan 2023 20:08:17 +0300 Subject: [PATCH] init(web): add http server on golang with authentication only And remove CGI --- CMakeLists.txt | 4 -- cgi/cgi.c | 114 -------------------------------------- web/web.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 118 deletions(-) delete mode 100644 cgi/cgi.c create mode 100644 web/web.go diff --git a/CMakeLists.txt b/CMakeLists.txt index be55947..03d1da6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,3 @@ add_library(tanabata SHARED ${TANABATA_SRC}) # Tanabata CLI app add_executable(tfm ${TANABATA_SRC} ${CLI_SRC}) - -# Authentication CGI app -add_executable(tfm-cgi ${TANABATA_SRC} cgi/cgi.c) -target_link_libraries(tfm-cgi fcgi ssl crypto pthread) diff --git a/cgi/cgi.c b/cgi/cgi.c deleted file mode 100644 index 2bfdbc3..0000000 --- a/cgi/cgi.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define TOKEN_RENTALTIME 604800 -#define TOKEN_SIZE 64 - -static time_t SID; -static char TOKEN[TOKEN_SIZE]; -static int socket_auth; -static int socket_cgi; - -int validate(FCGX_Request *request) { - if (time(NULL) - SID > TOKEN_RENTALTIME) { - return 1; - } - char token[TOKEN_SIZE]; - FCGX_GetStr(token, TOKEN_SIZE, request->in); - if (memcmp(token, TOKEN, TOKEN_SIZE) == 0) { - return 0; - } - return 1; -} - -static void *auth() { - FCGX_Request request; - if (FCGX_InitRequest(&request, socket_auth, FCGI_FAIL_ACCEPT_ON_INTR) != 0) { - exit(1); - } - unsigned char password[SHA256_DIGEST_LENGTH]; - FILE *passfile = fopen("/etc/tfm/password", "rb"); - if (passfile == NULL || - fread(password, 1, SHA256_DIGEST_LENGTH, passfile) < SHA256_DIGEST_LENGTH) { - exit(1); - } - fclose(passfile); - int rc; - char buffer[33]; - for (;;) { - static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&accept_mutex); - rc = FCGX_Accept_r(&request); - pthread_mutex_unlock(&accept_mutex); - if (rc < 0) { - break; - } - memset(buffer, 0, 33); - FCGX_GetStr(buffer, 32, request.in); - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256((const unsigned char *) buffer, strlen(buffer), hash); - if (memcmp(hash, password, SHA256_DIGEST_LENGTH) == 0) { - time(&SID); - uint64_t subtoken = SID; - SHA256((const unsigned char *) &subtoken, 8, hash); - for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { - sprintf(TOKEN + (i * 2), "%02x", hash[i]); - } - FCGX_PutS("Content-type: application/json\r\n\r\n" - "{\"status\":true,\"token\":\"", request.out); - FCGX_PutS(TOKEN, request.out); - FCGX_PutS("\"}\n", request.out); - FCGX_Finish_r(&request); - continue; - } - FCGX_PutS("Content-type: application/json\r\n\r\n" - "{\"status\":false}\n", request.out); - FCGX_Finish_r(&request); - } - return NULL; -} - -static void *tfmcgi() { - FCGX_Request request; - if (FCGX_InitRequest(&request, socket_cgi, FCGI_FAIL_ACCEPT_ON_INTR) != 0) { - exit(1); - } - int rc; - for (;;) { - static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&accept_mutex); - rc = FCGX_Accept_r(&request); - pthread_mutex_unlock(&accept_mutex); - if (rc < 0) { - break; - } - if (validate(&request) != 0) { - FCGX_PutS("Content-type: application/json\r\n\r\n" - "{\"status\":false}\n", request.out); - FCGX_Finish_r(&request); - continue; - } - FCGX_PutS("Content-type: application/json\r\n\r\n" - "{\"status\":true}\n", request.out); - FCGX_Finish_r(&request); - } - return NULL; -} - -int main() { - pthread_t thread_auth, thread_cgi; - FCGX_Init(); - if ((socket_auth = FCGX_OpenSocket("/tmp/tfm-auth.sock", 0)) == -1 || - (socket_cgi = FCGX_OpenSocket("/tmp/tfm-cgi.sock", 0)) == -1) { - return 1; - } - pthread_create(&thread_auth, NULL, auth, NULL); - pthread_create(&thread_cgi, NULL, tfmcgi, NULL); - pthread_join(thread_auth, NULL); - pthread_join(thread_cgi, NULL); - return 0; -} diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..9ab5b4b --- /dev/null +++ b/web/web.go @@ -0,0 +1,145 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "path" + "strconv" + "time" +) + +type JSON struct { + Status bool `json:"status,omitempty"` + Token string `json:"token,omitempty"` +} + +const TOKEN_VALIDTIME = 604800 + +var SID int64 = 0 +var TOKEN = "" + +func TokenGenerate(seed []byte) { + SID = time.Now().Unix() + value := SID + for _, char := range seed { + value += int64(char) + } + TOKEN = fmt.Sprintf("%x", sha256.Sum256([]byte(strconv.FormatInt(value, 16)))) +} + +func TokenValidate(token string) bool { + if time.Now().Unix()-SID >= TOKEN_VALIDTIME || token != TOKEN { + return false + } + return true +} + +func HandlerAuth(w http.ResponseWriter, r *http.Request) { + var buffer = make([]byte, sha256.Size) + var response = JSON{Status: false} + var passhash = make([]byte, sha256.Size) + var hash [sha256.Size]byte + var passlen = sha256.Size + var err error + passfile, err := os.Open("/etc/tfm/password") + if err != nil { + log.Fatalf("Failed to open password file: %s\n", err) + } + read, err := passfile.Read(passhash) + if err != nil { + log.Fatalf("Failed to read password file: %s\n", err) + } + if read != sha256.Size { + log.Fatalln("Invalid password file") + } + err = passfile.Close() + if err != nil { + log.Fatalf("Failed to close password file: %s\n", err) + } + _, err = r.Body.Read(buffer) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + for i := 0; i < sha256.Size; i++ { + if buffer[i] == 0 { + passlen = i + break + } + } + hash = sha256.Sum256(buffer[:passlen]) + if bytes.Equal(hash[:], passhash) { + TokenGenerate(buffer) + response.Status = true + response.Token = TOKEN + } + w.Header().Set("Content-Type", "application/json") + jsonData, err := json.Marshal(response) + if err != nil { + log.Fatalln(err) + } + _, err = w.Write(jsonData) + if err != nil { + log.Fatalln(err) + } +} + +func HandlerTFM(w http.ResponseWriter, r *http.Request) { + var request JSON + var response = JSON{Status: false} + var err error + r.Body = http.MaxBytesReader(w, r.Body, 1048576) + json_decoder := json.NewDecoder(r.Body) + json_decoder.DisallowUnknownFields() + err = json_decoder.Decode(&request) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if TokenValidate(request.Token) { + response.Status = true + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + jsonData, err := json.Marshal(response) + if err != nil { + log.Fatalln(err) + } + _, err = w.Write(jsonData) + if err != nil { + return + } + if err != nil { + log.Fatalln(err) + } +} + +func main() { + log.Println("Initializing...") + server := &http.Server{ + Addr: ":42776", + } + public_fs := http.FileServer(http.Dir("/var/www/tfm")) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" && path.Ext(r.URL.Path) == "" { + r.URL.Path += ".html" + } + public_fs.ServeHTTP(w, r) + }) + http.HandleFunc("/AUTH", HandlerAuth) + http.HandleFunc("/TFM", HandlerTFM) + tfm_fs := http.FileServer(http.Dir("/srv/data/tfm")) + http.Handle("/static", tfm_fs) + log.Println("Running...") + err := server.ListenAndServeTLS("/etc/ssl/certs/tfm.crt", "/etc/ssl/private/tfm.key") + if errors.Is(err, http.ErrServerClosed) { + log.Fatalln("Server closed") + } else if err != nil { + log.Fatalf("Error starting server: %s\n", err) + } +}