diff options
Diffstat (limited to 'src/qmail-qmaint.c')
-rw-r--r-- | src/qmail-qmaint.c | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/src/qmail-qmaint.c b/src/qmail-qmaint.c new file mode 100644 index 0000000..e83ab6f --- /dev/null +++ b/src/qmail-qmaint.c @@ -0,0 +1,594 @@ +/* + Based on an implementation of queue-fix 1.2 by Eric Huss +*/ +#include <unistd.h> +#include <sys/stat.h> +#include <pwd.h> +#include <grp.h> +#include "stralloc.h" +#include "direntry.h" +#include "fmt.h" +#include "fmtqfn.h" +#include "error.h" +#include "buffer.h" +#include "getln.h" +#include "str.h" +#include "open.h" +#include "fifo.h" +#include "scan.h" +#include "readsubdir.h" +#include "logmsg.h" +#include "exit.h" +#include "auto_qmail.h" +#include "auto_split.h" +#include "auto_uids.h" + +#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); +} |