268 lines
6.5 KiB
C
268 lines
6.5 KiB
C
|
/* Низкоуровневая программа для смены пароля на почтовый ящик @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;
|
||
|
}
|
||
|
|