/* Based on an implementation of queue-fix 1.2 by Eric Huss */ #include #include #include #include #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_queue.h" #include "auto_split.h" #include "auto_uids.h" #include "datetime.h" #include "date822fmt.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 dkim_dir = {0}; /*used for cleaning up dkim dir*/ stralloc query = {0}; /*used in interactive query function*/ stralloc birth_date = {0}; /*used to display dkim messages*/ 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."); } 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"); } /*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_uint(unum,uid)] = 0; strnum[fmt_uint(gnum,gid)] = 0; strnum[fmt_uint(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; } unsigned int datefmt(char *s,datetime_sec when) { unsigned int i; unsigned int len; struct datetime dt; datetime_tai(&dt,when); len = 0; i = fmt_str(s," Date: "); len += i; if (s) s += i; i = date822fmt(s,&dt); len += i; if (s) s += i; return len; } int date_make(datetime_sec when) { if (!stralloc_ready(&birth_date,datefmt(FMT_LEN,when))) return 0; birth_date.len = datefmt(birth_date.s,when); return 1; } int cleanup_dkim() { DIR *dir; direntry *d; struct stat st; int deleted = 0; if (chdir(auto_queue) == -1) err_chdir(); if (chdir("queue/dkim") == -1) return 0; if (!stralloc_copy(&dkim_dir,&queue_dir)) die_nomem(); if (!stralloc_cats(&dkim_dir,"dkim/")) die_nomem(); for (int i = 0; i < split_num; i++) { strnum[fmt_uint(strnum,i)] = 0; if (!stralloc_copy(&temp_dirname,&dkim_dir)) die_nomem(); if (!stralloc_cats(&temp_dirname,strnum)) die_nomem(); if (!stralloc_append(&temp_dirname,"/")) die_nomem(); dir = opendir(temp_dirname.s); if (!dir) continue; while ((d = readdir(dir))) { if (d->d_name[0] == '.') continue; /*check for dkim remnant */ if (!stralloc_copy(&temp_filename,&temp_dirname)) 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) == -1) continue; date_make(st.st_mtime); // message logmsg(WHO,0,INFO,B("Deleting DKIM remnant message: ",temp_filename.s,birth_date.s)); // delete if (unlink(temp_filename.s) == -1) { logmsg(WHO,111,ERROR,B("Can't remove file: ",temp_filename.s)); } // counter ++deleted; } } return deleted; } 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("dkim/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; } 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 queue 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; 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; /*check the bigtodo queue */ 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; /*check the dkim staging area */ 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)); } int delete_msg(unsigned long id) { struct stat st; int bounce = 1; if (chdir(auto_queue) == -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; unsigned int n; 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] || [-D]"); mess = argv[2]; flag_delete = 1; scan_ulong(mess,&id); } else if (!str_diff(argv[1],"-D")) { flag_delete = 2; } else logmsg(WHO,111,USAGE,"qmail-qmaint [-i] || [-d messid] || [-D]"); } if (!stralloc_copys(&queue_dir,auto_queue)) die_nomem(); if (!stralloc_cats(&queue_dir,"/queue/")) die_nomem(); logmsg(WHO,0,INFO,B("Checking s/qmail queue at: ",auto_queue,"/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 == 1) { if (!delete_msg(id)) logmsg(WHO,0,INFO,B("file ",mess," from queue deleted.")); } else if (flag_delete == 2) { n = cleanup_dkim(); strnum[fmt_uint(strnum,n)] = 0; logmsg(WHO,0,INFO,B(strnum," DKIM staging files from queue deleted.")); } else if (fix_names()) die_check(); logmsg(WHO,0,INFO,"done."); _exit (0); }