chpasswd/webserver.go

163 lines
4.1 KiB
Go
Raw Permalink Normal View History

2025-01-08 12:11:26 +00:00
// Веб-сервер для смены пароля на почтовый ящик @sch9.ru
//
// Copyright (c) 2025 Nikita Osokin.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package main
import (
"io"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"sync"
)
var status404_text []byte
var index_text []byte
var passwd_file_mutex sync.Mutex
func validate_chpasswd_input(reader io.Reader) ([]byte, bool) {
buf := make([]byte, 2049 * 4)
l, _ := reader.Read(buf)
eof_check := make([]byte, 1)
n, err := reader.Read(eof_check)
if n != 0 || err != io.EOF {
return nil, false
}
i := 0
lines_found := 0
line_start := 0
for i < l {
if buf[i] == '\n' {
if(i + 1 - line_start > 2049 || i + 1 - line_start == 1) {
return nil, false
}
line_start = i + 1
lines_found++
if(lines_found == 4) {
break
}
2025-01-08 12:11:26 +00:00
}
i++
}
2025-01-08 12:11:26 +00:00
if lines_found != 4 || i + 1 != l {
return nil, false
}
return buf, true
2025-01-08 12:11:26 +00:00
}
func handle_request(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/" {
writer.WriteHeader(http.StatusNotFound)
writer.Write(status404_text)
} else {
2025-01-08 12:11:26 +00:00
if request.Method == http.MethodGet {
writer.Write(index_text)
} else if request.Method == http.MethodPost {
passwd_file_mutex.Lock()
2025-01-08 12:11:26 +00:00
lines, could_read := validate_chpasswd_input(request.Body)
if !could_read {
writer.WriteHeader(http.StatusBadRequest)
2025-01-08 12:11:26 +00:00
writer.Write([]byte("250")) // -6, INVALID_INPUT
passwd_file_mutex.Unlock()
return
}
2025-01-08 12:11:26 +00:00
chpasswd := exec.Command("./chpasswd", "./chpasswd")
chpasswd.Stdin = strings.NewReader(string(lines))
chpasswd_res := chpasswd.Run()
if chpasswd_res != nil {
writer.WriteHeader(http.StatusBadRequest)
writer.Write([]byte(strconv.Itoa(chpasswd_res.(*exec.ExitError).ExitCode())))
2025-01-08 12:11:26 +00:00
passwd_file_mutex.Unlock()
2025-01-08 12:11:26 +00:00
return
}
2025-01-08 12:11:26 +00:00
update := exec.Command("/sbin/smtpctl", "update", "table", "creds")
update_res := update.Run()
if update_res != nil {
writer.WriteHeader(http.StatusBadRequest)
writer.Write([]byte("255")) // -1, INTERNAL_ERROR
2025-01-08 12:11:26 +00:00
passwd_file_mutex.Unlock()
2025-01-08 12:11:26 +00:00
return
}
2025-01-08 12:11:26 +00:00
writer.Write([]byte("0"))
2025-01-08 12:11:26 +00:00
passwd_file_mutex.Unlock()
return
2025-01-08 12:11:26 +00:00
} else {
writer.WriteHeader(http.StatusNotImplemented)
2025-01-08 12:11:26 +00:00
}
}
2025-01-08 12:11:26 +00:00
}
func handle_favicon(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNotFound)
}
func get_file_text(name string) []byte {
var err error
file, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
file_stat, err := file.Stat()
if err != nil {
file.Close()
log.Fatal(err)
}
text := make([]byte, file_stat.Size())
_, err = file.Read(text);
if err != nil {
file.Close()
log.Fatal(err)
}
file.Close()
return text
2025-01-08 12:11:26 +00:00
}
func main() {
index_text = get_file_text("index.html")
status404_text = get_file_text("404.html")
2025-01-08 12:11:26 +00:00
http.HandleFunc("/favicon.ico", handle_favicon)
http.HandleFunc("/", handle_request)
http.ListenAndServe(":62272", nil)
2025-01-08 12:11:26 +00:00
}