chpasswd/chpasswd.c

292 lines
7.2 KiB
C
Raw 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.
*/
#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 INVALID_INPUT -6
2025-01-08 12:11:26 +00:00
#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;
}
int copy_to(FILE * restrict src, FILE * restrict dest) {
2025-01-08 12:11:26 +00:00
size_t readen /* неправильные глаголы... */, written;
while(!feof(src)) {
readen = fread(buf, 1, BUF_LEN, src);
written = fwrite(buf, 1, readen, dest);
if(written != readen) {
return 0;
2025-01-08 12:11:26 +00:00
}
}
return 1;
2025-01-08 12:11:26 +00:00
}
int backup(FILE * restrict creds) {
2025-01-08 12:11:26 +00:00
int creds_old_fd = open_creds_old(OLD_PATH_BASE);
if(creds_old_fd == -1) {
return 0;
2025-01-08 12:11:26 +00:00
}
FILE * creds_old = fdopen(creds_old_fd, "w");
if(creds_old == NULL) {
close(creds_old_fd);
remove(creds_old_path);
return 0;
2025-01-08 12:11:26 +00:00
}
int ret = copy_to(creds, creds_old);
2025-01-08 12:11:26 +00:00
fclose(creds_old);
if(!ret) {
remove(creds_old_path);
}
return ret;
2025-01-08 12:11:26 +00:00
}
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;
}
// Формирование нового файла 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, * enc;
2025-01-08 12:11:26 +00:00
// Ищем и изменяем строку пользователя
2025-01-08 12:11:26 +00:00
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'; // Теперь мы ограничили часть строки, которая содержит хеш старого пароля
// и можем сравнить его с хешем данного пароля
2025-01-08 12:11:26 +00:00
enc = crypt(old_password, hash_begin);
2025-01-08 12:11:26 +00:00
if(strcmp(enc, hash_begin)) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INCORRECT_OLD_PASSWORD;
}
// Получаем хеш нового пароля
2025-01-08 12:11:26 +00:00
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]);
if(new_hash_len == 0) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INVALID_INPUT; // Скорее всего, в этом виноват пользователь, либо серверу плохо
}
buf[new_hash_len - 1] = '\0'; // Последний символ вывода smtpctl encrypt должен быть '\n'
2025-01-08 12:11:26 +00:00
fputs(buf, creds_new);
// Копируем строку пользователя и весь оставшийся файл до конца
2025-01-08 12:11:26 +00:00
fputc(':', creds_new);
fputs(hash_iter + 1, creds_new);
if(!copy_to(creds, creds_new)) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INTERNAL_ERROR;
}
rewind(creds);
if(!backup(creds)) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INTERNAL_ERROR;
}
2025-01-08 12:11:26 +00:00
fclose(creds_new);
fclose(creds);
remove("/etc/mail/creds");
rename("/etc/mail/creds.new", "/etc/mail/creds");
return 0;
}