/* ** Copyright (C) 1994, 1995 Enterprise Integration Technologies Corp. ** VeriFone Inc./Hewlett-Packard. All Rights Reserved. ** Kevin Hughes, kev@kevcom.com 3/11/94 ** Kent Landfield, kent@landfield.com 4/6/97 ** ** This program and library is free software; you can redistribute it and/or ** modify it under the terms of the GNU (Library) General Public License ** as published by the Free Software Foundation; either version 2 ** of the License, or any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU (Library) General Public License for more details. ** ** You should have received a copy of the GNU (Library) General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "hypermail.h" #include "setup.h" #include "struct.h" #include #ifdef HAVE_DIRENT_H #ifdef __LCC__ #include "../lcc/dirent.h" #include #else #include #endif #else #include #endif #ifdef GDBM #include "gdbm.h" #endif #ifdef HAVE_LIBFNV /* ** Since LCC won't use configure which moves fnv.h to this directory, ** include it in its original location. */ #ifdef __LCC__ #include "fnv/fnv.h" #else #include "fnv.h" #endif #endif /* HAVE_LIBFNV */ extern int snprintf(char *str, size_t size, const char *format, ...); /* ** Does a file exist? */ int isfile(char *path) { struct stat stbuf; if (stat(path, &stbuf)) return 0; return ((stbuf.st_mode & S_IFMT) == S_IFREG) ? 1 : 0; } /* ** Does a directory exist? */ int isdir(char *path) { struct stat stbuf; if (stat(path, &stbuf)) return 0; return ((stbuf.st_mode & S_IFMT) == S_IFDIR) ? 1 : 0; } /* ** This tries to create and chmod a directory. */ void check1dir(char *dir) { struct stat sbuf; if (stat(dir, &sbuf)) { /* ** LCC only has the short mkdir(). Fortunately, we do a chmod ** immediately afterward, so it's a don't care. */ #ifdef __LCC__ if (errno != ENOENT || mkdir(dir) < 0) { #else if (errno != ENOENT || mkdir(dir, set_dirmode) < 0) { #endif if (errno != EEXIST) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\".", lang[MSG_CANNOT_CREATE_DIRECTORY], dir); progerr(errmsg); } } if (set_showprogress) printf(" %s \"%s\", %s %o.\n", lang[MSG_CREATING_DIRECTORY], dir, lang[MSG_MODE], set_dirmode); if (chmod(dir, set_dirmode) == -1) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\" to %o.", lang[MSG_CANNOT_CHMOD], dir, set_dirmode); progerr(errmsg); } } } /* ** This tries to create and chmod a path to a directory. */ void checkdir(char *dir) { register char *p; struct stat sbuf; int ch; p = dir; if (*p && *p == '/') /* get off root */ p++; for (;; ++p) { if (!*p || *p == '/') { ch = *p; *p = '\0'; if (stat(dir, &sbuf)) { /* See comment in check1dir */ #ifdef __LCC__ if (errno != ENOENT || mkdir(dir) < 0) { #else if (errno != ENOENT || mkdir(dir, set_dirmode) < 0) { #endif if (errno != EEXIST) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\".", lang[MSG_CANNOT_CREATE_DIRECTORY], dir); progerr(errmsg); } } if (set_report_new_folder) { printf("%s\n", dir); } if (set_showprogress) printf(" %s \"%s\", %s %o.\n", lang[MSG_CREATING_DIRECTORY], dir, lang[MSG_MODE], set_dirmode); if (chmod(dir, set_dirmode) == -1) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\" to %o.", lang[MSG_CANNOT_CHMOD], dir, set_dirmode); progerr(errmsg); } } *p = ch; if (!*p) break; } } } char *getfilecontents(char *filename) { FILE *infile; struct stat finfo; char *retval; if ((infile = fopen(filename, "r")) == NULL) return (NULL); if (fstat(fileno(infile), &finfo) != 0) { (void)fclose(infile); return (NULL); } if (finfo.st_size == 0) { (void)fclose(infile); return (NULL); } else { if (!(retval = (char *)malloc((unsigned)finfo.st_size + 1))) { (void)fclose(infile); return (NULL); } if (!fread(retval, (size_t) finfo.st_size, 1, infile)) { (void)fclose(infile); free(retval); return (NULL); } *(retval + finfo.st_size) = '\0'; } (void)fclose(infile); return (retval); } /* ** expand_path - fill in values substituting for magic cookies ** ** Substitution cookies supported ** ** %d - two digit day of month (1-28/30/31) ** %D - three letter day of the week ** %m - two digit month of year (1-12) ** %M - three letter month of year (Jan, Feb, ..., Dec) ** %y - four digit year (1990,..2001) ** ** Returns: expanded path string */ char *dirpath(char *frmptr) { register char *aptr; char dtstr[DATESTRLEN]; char c; struct tm *now; time_t clk; struct Push buff; INIT_PUSH(buff); clk = time((time_t *)0); now = localtime(&clk); aptr = frmptr; while ((c = *aptr++)) { if (c == '%') { switch (*aptr++) { case '%': /* Add the % character */ PushByte(&buff, '%'); continue; case 'd': /* two digit day of month (1-31) */ sprintf(dtstr, "%.2d", now->tm_mday); PushString(&buff, dtstr); continue; case 'D': /* three letter day of week */ PushString(&buff, days[now->tm_wday]); continue; case 'j': /* julian date */ sprintf(dtstr, "%.3d", now->tm_yday); PushString(&buff, dtstr); continue; case 'm': /* two digit month of year (1-12) */ sprintf(dtstr, "%.2d", now->tm_mon + 1); PushString(&buff, dtstr); continue; case 'M': /* three letter month of year */ PushString(&buff, months[now->tm_mon]); continue; case 'y': /* 4 digit year */ sprintf(dtstr, "%.4d", now->tm_year + 1900); PushString(&buff, dtstr); continue; default: PushString(&buff, "%?"); continue; } /* end switch */ } PushByte(&buff, c); } /* end while */ RETURN_PUSH(buff); } /* ** Reads a configuration file if it exists and puts all the right ** values into the right variables. */ void readconfigs(char *path, int cmd_show_variables) { if (path && path[0] == '~') { char *ep; char tmppath[MAXFILELEN]; /*AUDIT biege: pathname + filename is more then 4KB long on linux */ struct passwd *pp; #ifndef __LCC__ /* ** Getting password data from /etc/passwd is pretty silly in ** Win9x systems since nearly everybody builds this file after ** they set up $HOME. Just skip this try at finding a default ** location for the config file and go on to try $HOME. */ if ((pp = getpwuid(getuid())) != NULL) { snprintf(tmppath, sizeof(tmppath), "%s%s", pp->pw_dir, path + 1); /* AUDIT biege: who gurantees that path+1 contains data? */ ConfigInit(tmppath); } else #endif if ((ep = getenv("HOME")) != NULL) { /* AUDIT biege: possible BOF.. but it's not setuid.. so why to care? */ snprintf(tmppath, sizeof(tmppath), "%s%s", ep, path + 1); /* AUDIT biege: who gurantees that path+1 contains data? */ ConfigInit(tmppath); } /* * So what happens here if the above two conditions fail ???? * Simply use the compiled in defaults ? */ } else { ConfigInit(path); if (set_showprogress && !cmd_show_variables) printf("%s: %s\n", lang[MSG_PATH], path); } } void symlink_latest() { /* ** Symlinks work so differently in Windows that I think we'll just ** skip that whole thing and ignore that option. */ #ifdef __LCC__ snprintf(errmsg, sizeof(errmsg), "WARNING: latest_folder not supported in Win32 environment.\n"); fprintf(stderr, "%s", errmsg); #else char filename[MAXFILELEN]; struct stat stbuf; if (!latest_folder_path) return; /* haven't created new folder this time? */ trio_snprintf(filename, MAXFILELEN, "%s%s", set_dir, set_latest_folder); if (!stat(filename, &stbuf) && unlink(filename)) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\" (latest_folder option).", lang[MSG_CANNOT_UNLINK], filename); progerr(errmsg); return; } if (symlink(latest_folder_path, filename)) { snprintf(errmsg, sizeof(errmsg), "%s \"%s\" (latest_folder option).", lang[MSG_CANNOT_CREATE_SYMLINK], filename); progerr(errmsg); return; } #endif } int find_max_msgnum() { DIR *dir; #ifdef HAVE_DIRENT_H struct dirent *entry; #else struct direct *entry; #endif int max_num = -1; int num; char *s_dir = strsav(set_dir); int len = (int)strlen(s_dir); if (len > 0 && s_dir[len - 1] == PATH_SEPARATOR) s_dir[len - 1] = 0; dir = opendir(s_dir); if (dir == NULL) return -1; #ifdef GDBM if (set_folder_by_date && set_usegdbm) { return loadoldheadersfromGDBMindex(set_dir, 1) - 1; } #endif if (set_msgsperfolder) { int max_folder = -1; char *tmpptr; while ((entry = readdir(dir))) { const char *p = entry->d_name; while (isdigit(*p)) ++p; if (!*p && p > entry->d_name) { num = atoi(entry->d_name); if (num > max_folder) { char *full_path; trio_asprintf(&full_path, "%s%d", set_dir, num); if (isdir(full_path)) max_folder = num; free(full_path); } } } closedir(dir); trio_asprintf(&tmpptr, "%s%d", set_dir, max_folder); free(s_dir); s_dir = tmpptr; if (max_folder == -1) return -1; dir = opendir(s_dir); if (dir == NULL) { snprintf(errmsg, sizeof(errmsg), "internal error find_max_msgnum opening \"%s\".", s_dir); progerr(errmsg); } } while ((entry = readdir(dir))) { const char *p = entry->d_name; while (isdigit(*p)) ++p; if (*p == '.' && p >= entry->d_name + 4) { ++p; if (!strcmp(p, set_htmlsuffix)) { num = atoi(entry->d_name); if (num > max_num) max_num = num; } } } closedir(dir); free(s_dir); return max_num; } /* ** Returns a buffer with the name of the message index name file. ** The caller has to free this buffer. */ char *messageindex_name(void) { char *buf; trio_asprintf(&buf, "%s%s", set_dir, "msgindex"); return (buf); } /* ** Returns the corresponding message number from the messageindex file. */ int find_max_msgnum_id() { int max_num = -1; FILE *fp; char line[MAXLINE]; int maxnum; int startnum; char *buf; /* open the index file */ buf = messageindex_name(); fp = fopen(buf, "r"); free(buf); if (fp) { fgets(line, sizeof(line), fp); if (2 == sscanf(line, "%04d %04d", &startnum, &maxnum)) max_num = maxnum; fclose(fp); } return max_num; } /* ** Get a list of msgid corresponding to hypermail msg numbers */ char **read_msgnum_id_table(int max_num) { char **table; int read_msgs; FILE *fp; char line[MAXLINE]; char *buf; if (max_num == -1) return NULL; table = (char **)calloc(sizeof(char *), max_num + 1); /* open the index file */ buf = messageindex_name(); fp = fopen(buf, "r"); free(buf); /* skip the max_msgnum (first) line */ fgets(line, sizeof(line), fp); read_msgs = 0; while (fgets(line, sizeof(line), fp) && read_msgs <= max_num) { char *msgid; int num; msgid = malloc(strlen(line) + 1); sscanf(line, "%d %s\n", &num, msgid); /* was the message skipped? */ if (read_msgs < num) { while (read_msgs < num) { table[read_msgs] = NULL; read_msgs++; } } table[read_msgs] = msgid; read_msgs++; } fclose(fp); return table; } /* ** Frees the table used to store the msgnum id correspondance */ void free_msgnum_id_table(char *table[], int max_num) { int i; if (!table) return; for (i = 0; i < max_num; i++) if (table[i]) free(table[i]); free(table); } int is_empty_archive() { DIR *dir; #ifdef HAVE_DIRENT_H struct dirent *entry; #else struct direct *entry; #endif int num_files = 0; char *s_dir = strsav(set_dir); int len = (int)strlen(s_dir); if (len > 0 && s_dir[len - 1] == PATH_SEPARATOR) s_dir[len - 1] = 0; dir = opendir(s_dir); if (dir == NULL) return 1; while ((entry = readdir(dir))) { const char *p = entry->d_name; if (*p != '.') { ++num_files; break; } } closedir(dir); free(s_dir); return num_files == 0; } static char *msgsperfolder_label(char *frmptr, int subdir_no) { register char *aptr; char dtstr[DATESTRLEN]; char c; struct Push buff; INIT_PUSH(buff); aptr = frmptr; while ((c = *aptr++)) { if (c == '%') { switch (*aptr++) { case '%': /* Add the % character */ PushByte(&buff, '%'); continue; case 'd': /* directory number, starting at 0 */ sprintf(dtstr, "%d", subdir_no); PushString(&buff, dtstr); continue; case 'D': /* directory number, starting with 1 */ sprintf(dtstr, "%d", subdir_no + 1); PushString(&buff, dtstr); continue; case 'm': /* number of first message in directory */ sprintf(dtstr, "%d", set_msgsperfolder * subdir_no); PushString(&buff, dtstr); continue; case 'M': /* number of last message possible */ sprintf(dtstr, "%d", set_msgsperfolder * (subdir_no + 1) - 1); PushString(&buff, dtstr); continue; default: PushString(&buff, "%?"); continue; } /* end switch */ } PushByte(&buff, c); } /* end while */ RETURN_PUSH(buff); } struct emailsubdir *msg_subdir(int msgnum, time_t date) { static struct emailsubdir *last_subdir; static struct emailsubdir *subdir; char s[DATESTRLEN]; char desc_buf[DATESTRLEN]; char *desc = NULL; char *fmt = set_describe_folder; if (set_msgsperfolder > 0) { int subdir_no = msgnum / set_msgsperfolder; trio_snprintf(s, DATESTRLEN, "%d/", subdir_no); if (!fmt) fmt = "%d"; desc = msgsperfolder_label(fmt, subdir_no); } else if (set_folder_by_date) { strftime(s, DATESTRLEN - 1, set_folder_by_date, localtime(&date)); if (!fmt) fmt = set_folder_by_date; strftime(desc_buf, DATESTRLEN, fmt, localtime(&date)); desc = strsav(desc_buf); if (s[0] && s[strlen(s) - 1] != '/') strcat(s, "/"); } else return NULL; subdir = last_subdir; if (!last_subdir || strcmp(s, last_subdir->subdir)) { subdir = new_subdir(s, last_subdir, desc, date); if (set_increment != -1) last_subdir = subdir; } if (desc) free(desc); return subdir; } /* ** Returns the filename we want to use. According to the convention, this ** can be the msgnumber, the msgid, or some other kind of name. */ char *message_name (struct emailinfo *email) { static char buffer[8 + sizeof (time_t) * 2 + 1]; #ifdef HAVE_LIBFNV if (set_nonsequential && email->msgid) { /* Call the FNV msg hash library */ Fnv32_t hash_val; hash_val = fnv_32_buf(email->msgid, strlen (email->msgid), FNV1_32_INIT); /* the line below is what we used before when the hash included the fromdate string, and we didn't concatenate the mail date. However, we changed strategies to avoid collisions. */ /* hash_val = fnv_32_str(email->fromdatestr, hash_val); */ sprintf (buffer, "%08x%08x", hash_val, email->fromdate); return buffer; } else { #endif /* HAVE_LIBFNV */ sprintf (buffer, "%.4d", email->msgnum); return buffer; #ifdef HAVE_LIBFNV } #endif /* HAVE_LIBFNV */ } /* * returns "" that links to to_email from the directory of * from_email, or from the set_dir directory if email is NULL. */ char *msg_href(struct emailinfo *to_email, struct emailinfo *from_email, bool generate_markup) /* note: you probably have to make a copy of * the buffer returned before the next call to this function. */ { static char buffer[MAXFILELEN + 11]; char *ptr; ptr = msg_relpath(to_email, from_email); if (generate_markup) trio_snprintf(buffer, MAXFILELEN + 11, "", ptr); else trio_snprintf(buffer, MAXFILELEN + 11, "%s", ptr); return buffer; } char *msg_relpath(struct emailinfo *to_email, struct emailinfo *from_email) /* called by msg_href() : note: you probably have to make a copy of * the buffer returned before the next call to this function. */ { static char buffer[MAXFILELEN]; char *name; name = message_name(to_email); if (!from_email && to_email->subdir) trio_snprintf(buffer, MAXFILELEN, "%s%s.%s", to_email->subdir->subdir, name, set_htmlsuffix); else if (!to_email->subdir || to_email->subdir == from_email->subdir) trio_snprintf(buffer, MAXFILELEN, "%s.%s", name, set_htmlsuffix); else trio_snprintf(buffer, MAXFILELEN, "%s%s%s.%s", to_email->subdir->rel_path_to_top, to_email->subdir->subdir, name, set_htmlsuffix); return buffer; } char *articlehtmlfilename(struct emailinfo *email) { char *buf; char *name; name = message_name(email); trio_asprintf(&buf, "%s%s.%s", email->subdir ? email->subdir->full_path : set_dir, name, set_htmlsuffix); return buf; } char *htmlfilename(const char *file, struct emailinfo *email, const char *suffix) { char *buf; trio_asprintf(&buf, "%s%s%s%s", email && email->subdir ? email->subdir->full_path : set_dir, file, *suffix ? "." : "", suffix); return buf; } char *haofname(struct emailinfo *email) { char *buf; trio_asprintf(&buf, "%s%s", email && email->subdir ? email->subdir->full_path : set_dir, HAOF_NAME); return buf; } /* matches_existing returns 0 if it finds a file with the same msgnum as argument eptr but different contents. A return value of 1 does not guarantee that they match, it only says a difference wasn't found (which can mean that no file was found). */ int matches_existing(int msgnum) { struct emailinfo *eptr; if (hashnumlookup(msgnum, &eptr) == NULL) return -1; #ifdef GDBM if (set_usegdbm) { char *indexname; GDBM_FILE gp; int num; trio_asprintf(&indexname, "%s%s", set_dir, GDBM_INDEX_NAME); if ((gp = gdbm_open(indexname, 0, GDBM_READER, 0, 0))) { /* we _can_ read the index */ datum content; datum key; int max_num; key.dptr = (char *)# key.dsize = (int)sizeof(num); num = -1; content = gdbm_fetch(gp, key); if (!content.dptr) max_num = -1; else max_num = atoi(content.dptr); if (eptr->msgnum <= max_num) { char *dp, *dp_end; char *name = NULL; char *email = NULL; char *date = NULL; char *msgid = NULL; char *fromdate = NULL; num = eptr->msgnum; key.dptr = (char *)# key.dsize = (int)sizeof(num); content = gdbm_fetch(gp, key); if (!(dp = content.dptr)) { return 1; } dp_end = dp + content.dsize; fromdate = dp; dp += strlen(dp) + 1; date = dp; dp += strlen(dp) + 1; name = dp; dp += strlen(dp) + 1; email = dp; dp += strlen(dp) + 1; dp += strlen(dp) + 1; msgid = dp; if (strcmp(msgid, eptr->msgid)) return 0; } gdbm_close(gp); } /* end case of able to read gdbm index */ free(indexname); } #endif if (!set_usegdbm) { int msgids_are_same; msgids_are_same = parse_old_html(msgnum, eptr, 0, 0, NULL, 1); return msgids_are_same != 0; } return 1; }