/* Низкоуровневая программа для смены пароля на почтовый ящик @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 #include #include #include #include #include #include #include #include #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 #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) { 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; } } return 1; } int backup(FILE * restrict creds) { int creds_old_fd = open_creds_old(OLD_PATH_BASE); if(creds_old_fd == -1) { return 0; } FILE * creds_old = fdopen(creds_old_fd, "w"); if(creds_old == NULL) { close(creds_old_fd); remove(creds_old_path); return 0; } int ret = copy_to(creds, creds_old); fclose(creds_old); if(!ret) { remove(creds_old_path); } return ret; } 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; // Ищем и изменяем строку пользователя 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'; // Теперь мы ограничили часть строки, которая содержит хеш старого пароля // и можем сравнить его с хешем данного пароля 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]); 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' fputs(buf, creds_new); // Копируем строку пользователя и весь оставшийся файл до конца 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; } fclose(creds_new); fclose(creds); remove("/etc/mail/creds"); rename("/etc/mail/creds.new", "/etc/mail/creds"); return 0; }