Изначальный коммит.
This commit is contained in:
commit
c3ad534959
6 changed files with 618 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
chpasswd
|
||||||
|
webserver
|
33
404.html
Normal file
33
404.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>:(</h1>
|
||||||
|
<p>404. Страница не найдена.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
19
COPYRIGHT
Normal file
19
COPYRIGHT
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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.
|
267
chpasswd.c
Normal file
267
chpasswd.c
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/* Низкоуровневая программа для смены пароля на почтовый ящик @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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <crypt.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define INTERNAL_ERROR -1
|
||||||
|
#define ILLEGAL_USERNAME -2
|
||||||
|
#define USER_NOT_FOUND -3
|
||||||
|
#define INCORRECT_OLD_PASSWORD -4
|
||||||
|
#define PASSWORD_MISMATCH -5
|
||||||
|
#define OLD_PATH_BASE 20
|
||||||
|
#define BUF_LEN 64 * 1024
|
||||||
|
|
||||||
|
#define walk_to_lf(x) for(iter = (x); *iter != '\n' && *iter != '\0'; iter++)
|
||||||
|
|
||||||
|
char creds_old_path[] = "/etc/mail/creds.old/XXXXXX";
|
||||||
|
char buf[BUF_LEN];
|
||||||
|
|
||||||
|
int open_creds_old(size_t i) {
|
||||||
|
if(i == OLD_PATH_BASE + 6) {
|
||||||
|
return open(creds_old_path, O_CREAT | O_EXCL | O_WRONLY, 0640);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(char c = '0'; c <= '9'; c++) {
|
||||||
|
creds_old_path[i] = c;
|
||||||
|
|
||||||
|
int fd = open_creds_old(i + 1);
|
||||||
|
if(fd != -1) {
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_to(FILE * restrict src, FILE * restrict dest, const char * destPath) {
|
||||||
|
size_t readen /* неправильные глаголы... */, written;
|
||||||
|
|
||||||
|
while(!feof(src)) {
|
||||||
|
readen = fread(buf, 1, BUF_LEN, src);
|
||||||
|
written = fwrite(buf, 1, readen, dest);
|
||||||
|
if(written != readen) {
|
||||||
|
fclose(src);
|
||||||
|
fclose(dest);
|
||||||
|
remove(destPath);
|
||||||
|
|
||||||
|
exit(INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void backup(FILE * restrict creds) {
|
||||||
|
int creds_old_fd = open_creds_old(OLD_PATH_BASE);
|
||||||
|
if(creds_old_fd == -1) {
|
||||||
|
fclose(creds);
|
||||||
|
|
||||||
|
exit(INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
FILE * creds_old = fdopen(creds_old_fd, "w");
|
||||||
|
if(creds_old == NULL) {
|
||||||
|
fclose(creds);
|
||||||
|
close(creds_old_fd);
|
||||||
|
remove(creds_old_path);
|
||||||
|
|
||||||
|
exit(INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_to(creds, creds_old, creds_old_path);
|
||||||
|
|
||||||
|
fclose(creds_old);
|
||||||
|
rewind(creds);
|
||||||
|
}
|
||||||
|
|
||||||
|
char * found_user_line(
|
||||||
|
char ** restrict line,
|
||||||
|
size_t * restrict n,
|
||||||
|
FILE * restrict creds,
|
||||||
|
char * restrict username,
|
||||||
|
FILE * restrict creds_new
|
||||||
|
) {
|
||||||
|
if(getline(line, n, creds) == -1) {
|
||||||
|
fclose(creds);
|
||||||
|
fclose(creds_new);
|
||||||
|
remove("/etc/mail/creds.new");
|
||||||
|
|
||||||
|
exit(USER_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
char * line_iter = *line;
|
||||||
|
char * username_iter = username;
|
||||||
|
|
||||||
|
while(*line_iter != '\0' && *line_iter != ':' && *username_iter != '\0') {
|
||||||
|
if(*line_iter != *username_iter) {
|
||||||
|
fputs(*line, creds_new);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
line_iter++;
|
||||||
|
username_iter++;
|
||||||
|
}
|
||||||
|
if(*line_iter != ':' || *username_iter != '\0') {
|
||||||
|
fputs(*line, creds_new);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line_iter + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char ** argv) {
|
||||||
|
char username[2049];
|
||||||
|
char old_password[2049];
|
||||||
|
char password[2049];
|
||||||
|
char password_repeat[2049];
|
||||||
|
fgets(username, 2049, stdin);
|
||||||
|
fgets(old_password, 2049, stdin);
|
||||||
|
fgets(password, 2049, stdin);
|
||||||
|
fgets(password_repeat, 2049, stdin);
|
||||||
|
|
||||||
|
// Очень неэффективный, но надёжный способ убрать '\n' в концах строк
|
||||||
|
char * iter;
|
||||||
|
|
||||||
|
walk_to_lf(username) {
|
||||||
|
if(*iter == ':' || !isprint((unsigned char)*iter)) {
|
||||||
|
return ILLEGAL_USERNAME;
|
||||||
|
} else {
|
||||||
|
*iter = tolower((unsigned char)*iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*iter = '\0';
|
||||||
|
|
||||||
|
walk_to_lf(old_password);
|
||||||
|
*iter = '\0';
|
||||||
|
|
||||||
|
walk_to_lf(password);
|
||||||
|
*iter = '\0';
|
||||||
|
size_t password_len = (size_t)(iter - password);
|
||||||
|
|
||||||
|
walk_to_lf(password_repeat);
|
||||||
|
*iter = '\0';
|
||||||
|
if(strcmp(password, password_repeat)) {
|
||||||
|
return PASSWORD_MISMATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FILE * creds = fopen("/etc/mail/creds", "r");
|
||||||
|
if(creds == NULL) {
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
backup(creds);
|
||||||
|
|
||||||
|
|
||||||
|
// Формирование нового файла creds
|
||||||
|
FILE * creds_new = fopen("/etc/mail/creds.new", "w");
|
||||||
|
if(creds_new == NULL) {
|
||||||
|
fclose(creds);
|
||||||
|
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char * line = NULL;
|
||||||
|
size_t n;
|
||||||
|
char * hash_begin, * hash_iter;
|
||||||
|
|
||||||
|
while((hash_begin = found_user_line(&line, &n, creds, username, creds_new)) == NULL);
|
||||||
|
fputs(username, creds_new);
|
||||||
|
fputc(':', creds_new);
|
||||||
|
|
||||||
|
for(hash_iter = hash_begin; *hash_iter != ':'; hash_iter++);
|
||||||
|
*hash_iter = '\0'; // Теперь мы ограничили часть строки, которая содержит хеш старого пароля
|
||||||
|
|
||||||
|
char * enc = crypt(old_password, hash_begin);
|
||||||
|
if(strcmp(enc, hash_begin)) {
|
||||||
|
fclose(creds);
|
||||||
|
fclose(creds_new);
|
||||||
|
remove("/etc/mail/creds.new");
|
||||||
|
|
||||||
|
return INCORRECT_OLD_PASSWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int inpipe[2], outpipe[2], status;
|
||||||
|
size_t new_hash_len;
|
||||||
|
pipe(inpipe);
|
||||||
|
pipe(outpipe);
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if(pid == -1) {
|
||||||
|
fclose(creds);
|
||||||
|
fclose(creds_new);
|
||||||
|
remove("/etc/mail/creds.new");
|
||||||
|
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
} else if(pid == 0) {
|
||||||
|
dup2(inpipe[0], 0);
|
||||||
|
close(inpipe[0]);
|
||||||
|
close(inpipe[1]);
|
||||||
|
dup2(outpipe[1], 1);
|
||||||
|
close(outpipe[0]);
|
||||||
|
close(outpipe[1]);
|
||||||
|
|
||||||
|
execl("/sbin/smtpctl", "smtpctl", "encrypt", (char *)NULL);
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
close(inpipe[0]);
|
||||||
|
close(outpipe[1]);
|
||||||
|
|
||||||
|
write(inpipe[1], password, password_len);
|
||||||
|
close(inpipe[1]);
|
||||||
|
|
||||||
|
wait(&status);
|
||||||
|
if(status) {
|
||||||
|
fclose(creds);
|
||||||
|
fclose(creds_new);
|
||||||
|
remove("/etc/mail/creds.new");
|
||||||
|
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_hash_len = read(outpipe[0], buf, 64 * 1024);
|
||||||
|
close(outpipe[0]);
|
||||||
|
// Основывается на том, что последний символ вывода smtpctl encrypt - '\n'
|
||||||
|
buf[new_hash_len - 1] = '\0';
|
||||||
|
fputs(buf, creds_new);
|
||||||
|
|
||||||
|
|
||||||
|
fputc(':', creds_new);
|
||||||
|
fputs(hash_iter + 1, creds_new);
|
||||||
|
copy_to(creds, creds_new, "/etc/mail/creds.new");
|
||||||
|
|
||||||
|
fclose(creds_new);
|
||||||
|
fclose(creds);
|
||||||
|
remove("/etc/mail/creds");
|
||||||
|
rename("/etc/mail/creds.new", "/etc/mail/creds");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
135
index.html
Normal file
135
index.html
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 2fr 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<h2>Смена пароля на почтовый ящик @sch9.ru</h2>
|
||||||
|
<input id="username" type="email" placeholder="Адрес эл. почты" />
|
||||||
|
<input id="old_password" type="password" placeholder="Старый пароль" />
|
||||||
|
<input id="password" type="password" placeholder="Новый пароль" />
|
||||||
|
<input id="password_repeat" type="password" placeholder="Повторите новый пароль" />
|
||||||
|
<input id="send" type="button" onclick="send_input()" value="Поменять пароль" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function response_body_to_status(response_body) {
|
||||||
|
if(response_body == "255") {
|
||||||
|
return "Внутренняя ошибка сервера.";
|
||||||
|
}
|
||||||
|
if(response_body == "254") {
|
||||||
|
return "Адрес эл. почты содержит недопустимые символы.";
|
||||||
|
}
|
||||||
|
if(response_body == "253") {
|
||||||
|
return "Адрес эл. почты не найден.";
|
||||||
|
}
|
||||||
|
if(response_body == "252") {
|
||||||
|
return "Старый пароль неправилен.";
|
||||||
|
}
|
||||||
|
if(response_body == "251") {
|
||||||
|
return "Новые пароли не совпадают.";
|
||||||
|
}
|
||||||
|
if(response_body == "250") {
|
||||||
|
return "Неправильно заполнены поля, или слишком длинные данные (макс. 2048 байтов на поле).";
|
||||||
|
}
|
||||||
|
return response_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function display_status(response) {
|
||||||
|
let send_button = document.getElementById("send");
|
||||||
|
let paragraph = document.createElement("p");
|
||||||
|
paragraph.id = "status_display";
|
||||||
|
let status_text;
|
||||||
|
|
||||||
|
if(response.status == 200) {
|
||||||
|
status_text = document.createTextNode("Пароль изменён.");
|
||||||
|
} else {
|
||||||
|
let response_body = await response.text();
|
||||||
|
|
||||||
|
status_text = document.createTextNode(response_body_to_status(response_body));
|
||||||
|
}
|
||||||
|
|
||||||
|
paragraph.append(status_text);
|
||||||
|
send_button.after(paragraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
function display_net_error(e) {
|
||||||
|
let send_button = document.getElementById("send");
|
||||||
|
let paragraph = document.createElement("p");
|
||||||
|
paragraph.id = "status_display";
|
||||||
|
let status_text = document.createTextNode("Сетевая ошибка. Возможно, вам стоит проверить своё интернет-соединение или подождать, а затем попробовать поменять пароль снова.");
|
||||||
|
|
||||||
|
paragraph.append(status_text);
|
||||||
|
send_button.after(paragraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
function append_to_input(input, offset, text) {
|
||||||
|
input.set(text, offset);
|
||||||
|
input[offset + text.length] = 10; // '\n'
|
||||||
|
return offset + text.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_input() {
|
||||||
|
let old_paragraph = document.getElementById("status_display");
|
||||||
|
if(old_paragraph != null) {
|
||||||
|
old_paragraph.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_encoder = new TextEncoder();
|
||||||
|
let username = text_encoder.encode(document.getElementById("username").value);
|
||||||
|
let old_password = text_encoder.encode(document.getElementById("old_password").value);
|
||||||
|
let password = text_encoder.encode(document.getElementById("password").value);
|
||||||
|
let password_repeat = text_encoder.encode(document.getElementById("password_repeat").value);
|
||||||
|
|
||||||
|
let input = new Uint8Array(username.length + old_password.length + password.length + password_repeat.length + 4);
|
||||||
|
|
||||||
|
let offset = append_to_input(input, 0, username);
|
||||||
|
offset = append_to_input(input, offset, old_password);
|
||||||
|
offset = append_to_input(input, offset, password);
|
||||||
|
append_to_input(input, offset, password_repeat);
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
"",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: input
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
display_status,
|
||||||
|
display_net_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
162
webserver.go
Normal file
162
webserver.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// Веб-сервер для смены пароля на почтовый ящик @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 == 0) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
line_start = i + 1
|
||||||
|
lines_found++
|
||||||
|
if(lines_found == 4) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines_found != 4 || i + 1 != l {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return buf, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_request(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.URL.Path != "/" {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
writer.Write(status404_text)
|
||||||
|
} else {
|
||||||
|
if request.Method == http.MethodGet {
|
||||||
|
writer.Write(index_text)
|
||||||
|
} else if request.Method == http.MethodPost {
|
||||||
|
passwd_file_mutex.Lock()
|
||||||
|
|
||||||
|
lines, could_read := validate_chpasswd_input(request.Body)
|
||||||
|
if !could_read {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
writer.Write([]byte("250")) // -6, INVALID_INPUT
|
||||||
|
|
||||||
|
passwd_file_mutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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())))
|
||||||
|
|
||||||
|
passwd_file_mutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
passwd_file_mutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write([]byte("0"))
|
||||||
|
|
||||||
|
passwd_file_mutex.Unlock()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
writer.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
index_text = get_file_text("index.html")
|
||||||
|
status404_text = get_file_text("404.html")
|
||||||
|
|
||||||
|
http.HandleFunc("/favicon.ico", handle_favicon)
|
||||||
|
http.HandleFunc("/", handle_request)
|
||||||
|
http.ListenAndServe(":62272", nil)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue