/* ** Copyright 1998 - 2006 Double Precision, Inc. See COPYING for ** distribution information. */ #include "config.h" #include "maildircache.h" #include "numlib/numlib.h" #include #include #include #include #include #include #if HAVE_UNISTD_H #include #endif #include #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_WAIT_H #include #endif #if HAVE_FCNTL_H #include #endif #if HAVE_DIRENT_H #include #define NAMLEN(dirent) strlen((dirent)->d_name) #else #define dirent direct #define NAMLEN(dirent) (dirent)->d_namlen #if HAVE_SYS_NDIR_H #include #endif #if HAVE_SYS_DIR_H #include #endif #if HAVE_NDIR_H #include #endif #endif #define exit(_a_) _exit(_a_) static const char * const *authvars; static char **authvals; static time_t expinterval; static time_t lastclean=0; static const char *cachedir; static const char *cacheowner; int maildir_cache_init(time_t n, const char *d, const char *o, const char * const *a) { unsigned x; expinterval=n; cachedir=d; cacheowner=o; authvars=a; for (x=0; a[x]; x++) ; if ((authvals=malloc(sizeof(char *)*(x+1))) == NULL) return (-1); for (x=0; a[x]; x++) authvals[x]=0; return (0); } static char *create_cache_name(const char *userid, time_t login_time) { int l; char buf[NUMBUFSIZE]; const char *p; char *q; char *f, *g; login_time /= expinterval; l=1; for (p=userid; *p; p++) { ++l; if ((unsigned char)*p < ' ' || *p == ';' || *p == '\'' || *p == ';') { fprintf(stderr, "CRIT: maildircache: invalid chars in userid: %s\n", p); return (NULL); } if (*p == '/' || *p == '+' || (int)(unsigned char)*p >= 127) l += 2; } g=malloc(l); if (!g) { perror("CRIT: maildircache: malloc failed"); return (NULL); } q=g; while (*userid) { if (*userid == '/' || *userid == '+' || (int)(unsigned char)*userid >= 127) { static char xdigit[]="0123456789ABCDEF"; *q++ = '+'; *q++ = xdigit[ (*userid >> 4) & 15 ]; *q++ = xdigit[ (*userid) & 15 ]; } else *q++ = *userid; ++userid; } *q=0; l=sizeof("//xx/xxxxxxx") + strlen(cachedir); l += strlen(libmail_str_time_t( login_time, buf)) + strlen(g); f=malloc(l); if (!f) { free(g); perror("CRIT: maildircache: malloc failed"); return (NULL); } strcat(strcat(strcat(strcpy(f, cachedir), "/"), buf), "/"); strncpy(buf, g, 2); buf[2]=0; while (strlen(buf) < 2) strcat(buf, "+"); strcat(strcat(strcat(f, buf), "/"), g); free(g); return (f); } static pid_t childproc= -1; static int childpipe; void maildir_cache_start() { int pipefd[2]; char buf[2048]; int i, j; char *userid, *login_time, *data; time_t login_time_n; char *f; FILE *fp; if (pipe(pipefd) < 0) { perror("CRIT: maildircache: pipe() failed"); return; } while ((childproc=fork()) < 0) { sleep(5); } if (childproc) { close(pipefd[0]); childpipe=pipefd[1]; return; } close(pipefd[1]); i=0; for (;;) { if (i >= sizeof(buf)-1) { close(pipefd[0]); /* Problems */ fprintf(stderr, "CRIT: maildircache: Max cache buffer overflow.\n"); exit(1); } j=read(pipefd[0], buf+i, sizeof(buf)-1-i); if (j < 0) { perror("CRIT: maildircache: Cache create failure"); exit(1); } if (j == 0) break; i += j; } close(pipefd[0]); buf[i]=0; { struct passwd *pwd=getpwnam(cacheowner); if (!pwd || setgid(pwd->pw_gid) || setuid(pwd->pw_uid)) { fprintf(stderr, "CRIT: maildircache: Cache create failure - cannot change to user %s\n", cacheowner); exit(1); } } if (strncmp(buf, "CANCELLED\n", 10) == 0) exit (0); userid=buf; if ((login_time=strchr(userid, ' ')) == 0) { fprintf(stderr, "CRIT: maildircache: Cache create failure - authentication process crashed.\n"); exit(1); } *login_time++=0; if ((data=strchr(login_time, ' ')) == 0) { fprintf(stderr, "CRIT: maildircache: Cache create failure - authentication process crashed.\n"); exit(1); } *data++=0; login_time_n=0; while (*login_time >= '0' && *login_time <= '9') login_time_n = login_time_n * 10 + (*login_time++ -'0'); f=create_cache_name(userid, login_time_n); if (!f) exit(0); if ((fp=fopen(f, "w")) == 0) /* Try creating subdirs */ { char *p=f+strlen(cachedir); while (p && *p == '/') { *p=0; mkdir(f, 0700); *p='/'; p=strchr(p+1, '/'); } if ((fp=fopen(f, "w")) == 0) { fprintf(stderr, "CRIT: maildircache: Cache create failure - unable to create file %s.\n", f); exit(1); } } if ( fwrite(data, strlen(data), 1, fp) != 1 || fflush(fp) || ferror(fp)) { fclose(fp); unlink(f); /* Problems */ free(f); fprintf(stderr, "CRIT: maildircache: Cache create failure - write error.\n"); exit(1); } else fclose(fp); free(f); exit(0); } static int savebuf(char *p, int l) { while (l) { int n=write(childpipe, p, l); if (n <= 0) return (-1); p += n; l -= n; } return (0); } void maildir_cache_save(const char *a, time_t b, const char *homedir, uid_t u, gid_t g) { char buf[2048]; char buf2[NUMBUFSIZE]; pid_t p; int waitstat; strcat(strcpy(buf, a), " "); strcat(strcat(buf, libmail_str_time_t(b, buf2)), " "); strcat(strcat(buf, libmail_str_uid_t(u, buf2)), " "); strcat(strcat(buf, libmail_str_gid_t(g, buf2)), " "); strncat(buf, homedir, sizeof(buf)-2-strlen(homedir)); strcat(buf, "\n"); if (savebuf(buf, strlen(buf)) == 0) { int i; for (i=0; authvars[i]; i++) { const char *p; strcat(strcpy(buf, authvars[i]), "="); p=getenv(authvars[i]); if (!p || strlen(p)+strlen(buf) >= sizeof(buf)-2 || strchr(p, '\n')) continue; strcat(strcat(buf, p), "\n"); if (savebuf(buf, strlen(buf))) break; } } close(childpipe); while ((p=wait(&waitstat)) != -1 && p != childproc) ; childproc= -1; } void maildir_cache_cancel() { if (childproc > 0) { if (write(childpipe, "CANCELLED\n", 10) < 0) perror("write"); close(childpipe); } } int maildir_cache_search(const char *a, time_t b, int (*callback_func)(uid_t, gid_t, const char *, void *), void *callback_arg) { char *f=create_cache_name(a, b); FILE *fp; uid_t u; gid_t g; char dir[1024]; int n; int c; if (!f) return (-1); fp=fopen(f, "r"); free(f); if (!fp) return (-1); u=0; while ((c=getc(fp)) != ' ') { if (c < '0' || c > '9') { fclose(fp); return (-1); } u=u*10 + (c-'0'); } g=0; while ((c=getc(fp)) != ' ') { if (c < '0' || c > '9') { fclose(fp); return (-1); } g=g*10 + (c-'0'); } for (n=0; (c=getc(fp)) != EOF; n++) { if (c == '\n') break; if (n >= sizeof(dir)-1) { fclose(fp); fprintf(stderr, "CRIT: maildircache: Cache record overflow.\n"); return (-1); } dir[n]=(char)c; } dir[n]=0; if ((n=(*callback_func)(u, g, dir, callback_arg)) != 0) { fclose(fp); return (n); } if (c != EOF) { while (fgets(dir, sizeof(dir), fp)) { char *q; if ( (q=strchr(dir, '\n')) == 0) { fclose(fp); fprintf(stderr, "CRIT: maildircache: Cache record overflow.\n"); return (-1); } *q=0; for (n=0; authvars[n]; n++) { int l=strlen(authvars[n]); if (strncmp(dir, authvars[n], l) == 0 && dir[l] == '=') { char *s=strdup(dir); if (!s) { fclose(fp); perror("CRIT: maildircache: malloc failed"); return (-1); } putenv(s); if (authvals[n]) free(authvals[n]); authvals[n]=s; break; } } } fclose(fp); } return (0); } struct purge_list { struct purge_list *next; char *n; } ; static void add_purge_list(struct purge_list **p, const char *a) { char *c=malloc(strlen(a) + 1); struct purge_list *pp; if (!c) return; pp=malloc(sizeof(struct purge_list)); if (!pp) { free(c); return; } pp->next=*p; *p=pp; pp->n=c; strcpy(c, a); } static void rmrf(const char *); void maildir_cache_purge() { time_t now; pid_t p; int waitstat; struct passwd *pw; struct purge_list *pl; DIR *dirp; struct dirent *de; struct sigaction sa, oldsa; time(&now); if (lastclean && lastclean >= now - expinterval) return; lastclean=now; memset(&sa, 0, sizeof(sa)); sa.sa_handler=SIG_DFL; if (sigaction(SIGCHLD, &sa, &oldsa) < 0) { perror("sigaction"); return; } p=fork(); if (p < 0) return; if (p) { pid_t p2; while ((p2=wait(&waitstat)) >= 0 && p2 != p) ; sigaction(SIGCHLD, &oldsa, NULL); return; } p=fork(); if (p) exit(0); pw=getpwnam(cacheowner); if (!pw) { fprintf(stderr, "CRIT: maildircache: no such user %s - cannot purge login cache dir\n", cacheowner); exit(0); } if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { fprintf(stderr, "CRIT: maildircache: cannot change to uid/gid for %s - cannot purge login cache dir\n", cacheowner); exit(0); } if (chdir(cachedir)) { fprintf(stderr, "CRIT: maildircache: cannot change dir to %s\n", cachedir); exit(0); } pl=NULL; dirp=opendir("."); now /= expinterval; --now; while (dirp && (de=readdir(dirp)) != NULL) { if (!isdigit((int)(unsigned char)de->d_name[0])) continue; if (atol(de->d_name) >= now) continue; add_purge_list(&pl, de->d_name); } if (dirp) closedir(dirp); while (pl) { struct purge_list *p=pl; pl=pl->next; rmrf(p->n); free(p->n); free(p); } exit(0); } static void rmrf(const char *d) { DIR *dirp; struct dirent *de; struct purge_list *pl=NULL, *p; if (chdir(d)) return; dirp=opendir("."); while (dirp && (de=readdir(dirp)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; add_purge_list(&pl, de->d_name); } if (dirp) closedir(dirp); while (pl) { p=pl; pl=pl->next; if (unlink(p->n)) rmrf(p->n); free(p->n); free(p); } if (chdir("..") < 0 || rmdir(d) < 0) { fprintf(stderr, "CRIT: maildircache: cannot chdir to .. while purging login cache\n"); exit(1); } }