/* Based on an implementation of queue-fix 1.2 by Eric Huss */ #include #include #include #include #include "buffer.h" #include "direntry.h" #include "error.h" #include "exit.h" #include "fifo.h" #include "fmt.h" #include "getln.h" #include "logmsg.h" #include "open.h" #include "scan.h" #include "str.h" #include "stralloc.h" #include "fmtqfn.h" #include "readsubdir.h" #ifdef USE_CONFIG #include "fehsqm-config.h" #else #include "auto_qmail.h" #include "auto_split.h" #include "auto_uids.h" #endif #define WHO "qmail-qmaint" stralloc queue_dir = {0}; /*the root queue dir with trailing slash*/ stralloc check_dir = {0}; /*the current directory being checked*/ stralloc temp_dirname = {0}; /*temporary used for checking directories */ stralloc temp_filename = {0}; /*temporary used for checking individuals*/ stralloc old_name = {0}; /*used in rename*/ stralloc new_name = {0}; /*used in rename*/ stralloc mess_dir = {0}; /*used for renaming in mess dir*/ stralloc query = {0}; /*used in interactive query function*/ char strnum[FMT_ULONG]; int flag_interactive = 0; int flag_dircreate = 0; int flag_filecreate = 0; int flag_permfix = 0; int flag_namefix = 0; int flag_delete = 0; int qmailq_uid; int qmails_uid; int qmailr_uid; int qmail_gid; int split_num; void die_make(char *name) { logmsg(WHO, 111, ERROR, B("Failed to make: ", name)); } void die_user(char *user) { logmsg(WHO, 111, ERROR, B("Failed to determine uid of: ", user)); } void die_group(char *group) { logmsg(WHO, 111, ERROR, B("Failed to determine gid of: ", group)); } void die_check() { logmsg( WHO, 111, ERROR, "Failed while checking directory structure. \nEnsure the given queue exists and you have " "permission to access it."); } void die_recon() { logmsg( WHO, 110, ERROR, "Failed to reconstruct queue. \nEnsure the queue exists and you have permission to modify it."); } void die_nomem() { logmsg(WHO, 110, ERROR, "Out of memory."); } /*returns 1==yes, 0==no*/ int confirm() { int match; if (getln(buffer_0, &query, &match, '\n')) return 0; if (!match) return 0; if (query.s[0] == 'y' || query.s[0] == 'Y' || query.s[0] == '\n') return 1; return 0; } /*gid may be -1 on files for "unknown*/ #define DIRS \ logmsg(WHO, 0, WARN, "It looks like some directories don't exist, should I create them? (Y/n)") #define FILES logmsg(WHO, 0, WARN, "It looks like some files don't exist, should I create them? (Y/n)") #define PERMS \ logmsg(WHO, 0, WARN, B("It looks like permissions are wrong for ", name, " should I fix them? (Y/n)")) #define CPERMS logmsg(WHO, 0, WARN, B("Changing permissions: ", name, " => ", pnum)) #define OWNER \ logmsg(WHO, 0, WARN, B("It looks like ownerships are wrong for ", name, " should I fix them? (Y/n)")) #define COWNER logmsg(WHO, 0, WARN, B("Changing ownership: ", name, " => ", unum, "/", gnum)) int check_item(char *name, int uid, int gid, int perm, char type, int size) { struct stat st; int fd; char num[12]; char unum[12]; char gnum[12]; char pnum[12]; /*check for existence and proper credentials*/ strnum[fmt_ulong(unum, uid)] = 0; strnum[fmt_ulong(gnum, gid)] = 0; strnum[fmt_ulong(pnum, perm)] = 0; switch (type) { case 'd': /*directory*/ if (stat(name, &st)) { if (errno != ENOENT) return -1; if (!flag_dircreate && flag_interactive) { DIRS; if (!confirm()) return -1; flag_dircreate = 1; } /*create it*/ logmsg(WHO, 0, INFO, B("Creating directory: ", name)); if (mkdir(name, perm)) die_make(name); CPERMS; if (chmod(name, perm)) die_make(name); COWNER; if (chown(name, uid, gid)) die_make(name); return 0; } /*check the values*/ if (st.st_uid != uid || st.st_gid != gid) { if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; } COWNER; if (chown(name, uid, gid)) die_make(name); } if ((st.st_mode & 07777) != perm) { if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; } CPERMS; if (chmod(name, perm)) die_make(name); } return 0; case 'f': /*regular file*/ if (stat(name, &st)) return -1; /*check the values*/ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) { if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; } COWNER; if (chown(name, uid, gid)) die_make(name); } if ((st.st_mode & 07777) != perm) { if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; } CPERMS; if (chmod(name, perm)) die_make(name); } return 0; case 'z': /*regular file with a size*/ if (stat(name, &st)) { if (errno != ENOENT) return -1; if (!flag_filecreate && flag_interactive) { FILES; if (!confirm()) return -1; flag_filecreate = 1; } /*create it*/ strnum[fmt_ulong(num, size)] = 0; logmsg(WHO, 0, INFO, B("Creating: ", name, " with size ", num)); fd = open_trunc(name); if (fd == -1) die_make(name); while (size--) { if (write(fd, "", 1) != 1) die_make(name); } close(fd); CPERMS; if (chmod(name, perm)) die_make(name); COWNER; if (chown(name, uid, gid)) die_make(name); return 0; } /*check the values*/ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) { if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; } COWNER; if (chown(name, uid, gid)) die_make(name); } if ((st.st_mode & 07777) != perm) { if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; } CPERMS; if (chmod(name, perm)) die_make(name); } if (st.st_size != size) { logmsg( WHO, 0, WARN, B("File ", name, " has not the right size. I will not fix it, please investigate.")); } return 0; case 'p': /*a named pipe*/ if (stat(name, &st)) { if (errno != ENOENT) return -1; if (!flag_filecreate && flag_interactive) { FILES; if (!confirm()) return -1; flag_filecreate = 1; } /*create it*/ logmsg(WHO, INFO, 0, B("Creating fifo: ", name)); if (fifo_make(name, perm)) die_make(name); CPERMS; if (chmod(name, perm)) die_make(name); COWNER; if (chown(name, uid, gid)) die_make(name); return 0; } /*check the values*/ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) { if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; } COWNER; if (chown(name, uid, gid)) die_make(name); } if ((st.st_mode & 07777) != perm) { if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; } CPERMS; if (chmod(name, perm)) die_make(name); } return 0; } return 0; } int check_files(char *directory, int uid, int gid, int perm) { DIR *dir; direntry *d; dir = opendir(directory); if (!dir) return -1; while ((d = readdir(dir))) { if (d->d_name[0] == '.') continue; if (!stralloc_copys(&temp_filename, directory)) die_nomem(); if (!stralloc_append(&temp_filename, "/")) die_nomem(); if (!stralloc_cats(&temp_filename, d->d_name)) die_nomem(); if (!stralloc_0(&temp_filename)) die_nomem(); if (check_item(temp_filename.s, uid, gid, perm, 'f', 0)) { closedir(dir); return -1; } } closedir(dir); return 0; } void warn_files(char *directory) { DIR *dir; direntry *d; int found = 0; dir = opendir(directory); if (!dir) return; while ((d = readdir(dir))) { if (d->d_name[0] == '.') continue; found = 1; break; } closedir(dir); if (found) logmsg( WHO, 0, WARN, B("Found files in ", directory, " that shouldn't be there. I will not remove them. You should consider checking it out.")); } int check_splits(char *directory, int dir_uid, int dir_gid, int dir_perm, int file_gid, int file_perm) { DIR *dir; direntry *d; int i; for (i = 0; i < split_num; i++) { strnum[fmt_ulong(strnum, i)] = 0; if (!stralloc_copys(&temp_dirname, directory)) die_nomem(); if (!stralloc_append(&temp_dirname, "/")) die_nomem(); if (!stralloc_cats(&temp_dirname, strnum)) die_nomem(); if (!stralloc_0(&temp_dirname)) die_nomem(); /*check the split dir*/ if (check_item(temp_dirname.s, dir_uid, dir_gid, dir_perm, 'd', 0)) return -1; /*check its contents*/ dir = opendir(temp_dirname.s); if (!dir) return -1; while ((d = readdir(dir))) { if (d->d_name[0] == '.') continue; if (!stralloc_copys(&temp_filename, temp_dirname.s)) die_nomem(); if (!stralloc_append(&temp_filename, "/")) die_nomem(); if (!stralloc_cats(&temp_filename, d->d_name)) die_nomem(); if (!stralloc_0(&temp_filename)) die_nomem(); if (check_item(temp_filename.s, dir_uid, file_gid, file_perm, 'f', 0)) { closedir(dir); return -1; } } closedir(dir); } return 0; } int rename_mess(char *dir, char *part, char *new_part, char *old_filename, char *new_filename) { if (flag_interactive && !flag_namefix) { logmsg(WHO, 0, INFO, "It looks like some files need to be renamed, should I rename them? (Y/n)\n"); if (!confirm()) return -1; flag_namefix = 1; } /*prepare the old filename*/ if (!stralloc_copy(&old_name, &queue_dir)) die_nomem(); if (!stralloc_cats(&old_name, dir)) die_nomem(); if (!stralloc_cats(&old_name, part)) die_nomem(); if (!stralloc_append(&old_name, "/")) die_nomem(); if (!stralloc_cats(&old_name, old_filename)) die_nomem(); if (!stralloc_0(&old_name)) die_nomem(); /*prepare the new filename*/ if (!stralloc_copy(&new_name, &queue_dir)) die_nomem(); if (!stralloc_cats(&new_name, dir)) die_nomem(); if (!stralloc_cats(&new_name, new_part)) die_nomem(); if (!stralloc_append(&new_name, "/")) die_nomem(); if (!stralloc_cats(&new_name, new_filename)) die_nomem(); if (!stralloc_0(&new_name)) die_nomem(); logmsg(WHO, 0, INFO, B("Renaming ", old_name.s, " to ", new_name.s)); if (rename(old_name.s, new_name.s)) { if (errno != ENOENT) return -1; } return 0; } int fix_part(char *part) { DIR *dir; direntry *d; struct stat st; char inode[FMT_ULONG]; char new_part[FMT_ULONG]; int old_inode; int part_num; int correct_part_num; scan_uint(part, &part_num); if (!stralloc_copy(&mess_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&mess_dir, "mess/")) die_nomem(); if (!stralloc_cats(&mess_dir, part)) die_nomem(); if (!stralloc_0(&mess_dir)) die_nomem(); dir = opendir(mess_dir.s); if (!dir) return -1; while ((d = readdir(dir))) { if (d->d_name[0] == '.') continue; /*check from mess*/ if (!stralloc_copys(&temp_filename, mess_dir.s)) die_nomem(); if (!stralloc_append(&temp_filename, "/")) die_nomem(); if (!stralloc_cats(&temp_filename, d->d_name)) die_nomem(); if (!stralloc_0(&temp_filename)) die_nomem(); if (stat(temp_filename.s, &st)) { closedir(dir); return -1; } /*check that filename == inode number*/ /*check that inode%auto_split == part_num*/ scan_uint(d->d_name, &old_inode); correct_part_num = st.st_ino % split_num; if (st.st_ino != old_inode || part_num != correct_part_num) { /*rename*/ inode[fmt_ulong(inode, st.st_ino)] = 0; new_part[fmt_ulong(new_part, correct_part_num)] = 0; if (rename_mess("mess/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("info/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("local/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("remote/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("todo/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("intd/", part, new_part, d->d_name, inode)) { closedir(dir); return -1; } if (rename_mess("bounce", "", "", d->d_name, inode)) { closedir(dir); return -1; } } } closedir(dir); return 0; } int fix_names() { int i; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "mess")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); for (i = 0; i < split_num; i++) { strnum[fmt_ulong(strnum, i)] = 0; if (fix_part(strnum)) return -1; } return 0; } int check_dirs() { /*check root existence*/ if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0750, 'd', 0)) return -1; /*check the bigtodo queue */ if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "info")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0700, 'd', 0)) return -1; if (check_splits(check_dir.s, qmails_uid, qmail_gid, 0700, qmail_gid, 0600)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "mess")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0750, 'd', 0)) return -1; if (check_splits(check_dir.s, qmailq_uid, qmail_gid, 0750, -1, 0644)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "remote")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0700, 'd', 0)) return -1; if (check_splits(check_dir.s, qmails_uid, qmail_gid, 0700, qmail_gid, 0600)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "local")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0700, 'd', 0)) return -1; if (check_splits(check_dir.s, qmails_uid, qmail_gid, 0700, qmail_gid, 0600)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "intd")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0700, 'd', 0)) return -1; if (check_splits(check_dir.s, qmailq_uid, qmail_gid, 0700, qmail_gid, 0600)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "todo")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0750, 'd', 0)) return -1; if (check_splits(check_dir.s, qmailq_uid, qmail_gid, 0750, -1, 0644)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "dkim")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0750, 'd', 0)) return -1; if (check_splits(check_dir.s, qmailq_uid, qmail_gid, 0750, qmail_gid, 0644)) return -1; /*check the others*/ if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "bounce")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0700, 'd', 0)) return -1; if (check_files(check_dir.s, qmails_uid, qmail_gid, 0600)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "pid")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0700, 'd', 0)) return -1; warn_files(check_dir.s); /*lock has special files that must exist*/ if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "lock")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailq_uid, qmail_gid, 0750, 'd', 0)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "lock/sendmutex")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0600, 'z', 0)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "lock/tcpto")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmailr_uid, qmail_gid, 0644, 'z', 1024)) return -1; if (!stralloc_copy(&check_dir, &queue_dir)) die_nomem(); if (!stralloc_cats(&check_dir, "lock/trigger")) die_nomem(); if (!stralloc_0(&check_dir)) die_nomem(); if (check_item(check_dir.s, qmails_uid, qmail_gid, 0622, 'p', 0)) return -1; return 0; } /* stolen from qmail-send */ stralloc fn = {0}; void fnmake_init() { while (!stralloc_ready(&fn, FMTQFN)) die_nomem(); } void fnmake_local(unsigned long id) { fn.len = fmtqfn(fn.s, "local/", id, 1); } void fnmake_remote(unsigned long id) { fn.len = fmtqfn(fn.s, "remote/", id, 1); } void fnmake_mess(unsigned long id) { fn.len = fmtqfn(fn.s, "mess/", id, 1); } void fnmake_dkim(unsigned long id) { fn.len = fmtqfn(fn.s, "dkim/", id, 1); } void fnmake_info(unsigned long id) { fn.len = fmtqfn(fn.s, "info/", id, 1); } void fnmake_bounce(unsigned long id) { fn.len = fmtqfn(fn.s, "bounce/", id, 0); } void warn_unlink(unsigned long id) { char foo[FMT_ULONG]; foo[fmt_ulong(foo, id)] = 0; logmsg(WHO, 99, WARN, B("no such file to unlink #", foo)); } void err_unlink(unsigned long id) { char foo[FMT_ULONG]; foo[fmt_ulong(foo, id)] = 0; logmsg(WHO, 100, ERROR, B("trouble with unlinking #", foo)); } void err_chdir() { logmsg(WHO, 110, FATAL, "unable to chdir"); } int delete_msg(unsigned long id) { struct stat st; int bounce = 1; if (chdir(auto_qmail) == -1) err_chdir(); if (chdir("queue") == -1) err_chdir(); fnmake_init(); fnmake_mess(id); // regular message pre-processed if (stat(fn.s, &st) == -1) err_unlink(id); else bounce = 0; if (!bounce && unlink(fn.s) == -1) if (errno != ENOENT) err_unlink(id); fnmake_info(id); // not delivered yet if (!stat(fn.s, &st)) if (unlink(fn.s) == -1) if (errno != ENOENT) err_unlink(id); if (bounce) { fnmake_bounce(id); if (!stat(fn.s, &st)) { warn_unlink(id); return 1; } if (unlink(fn.s) == -1) if (errno != ENOENT) err_unlink(id); } fnmake_remote(id); if (!stat(fn.s, &st)) if (unlink(fn.s) == -1) if (errno != ENOENT) err_unlink(id); fnmake_local(id); if (!stat(fn.s, &st)) if (unlink(fn.s) == -1) if (errno != ENOENT) err_unlink(id); return 0; } int main(int argc, char **argv) { char *mess = 0; unsigned long id = 0; if (argc > 1) { if (!str_diff(argv[1], "-i")) { flag_interactive = 1; } else if (!str_diff(argv[1], "-d")) { if (!argv[2]) logmsg(WHO, 111, USAGE, "qmail-qmaint [-i] || [-d messid]"); mess = argv[2]; flag_delete = 1; scan_ulong(mess, &id); } } if (!stralloc_copys(&queue_dir, auto_qmail)) die_nomem(); if (!stralloc_cats(&queue_dir, "/queue/")) die_nomem(); logmsg(WHO, 0, INFO, B("Checking s/qmail queue at: ", auto_qmail, "/queue/")); /* get constants */ qmailq_uid = auto_uidq; qmails_uid = auto_uids; qmailr_uid = auto_uidr; qmail_gid = auto_gidq; split_num = auto_split; /*check that all the proper directories exist with proper credentials*/ if (check_dirs()) die_check(); if (flag_delete) { if (!delete_msg(id)) logmsg(WHO, 0, INFO, B("file ", mess, " from queue deleted.")); } else if (fix_names()) { die_check(); } logmsg(WHO, 0, INFO, "done."); _exit(0); }