/* ======================================================================== * Copyright 2013-2022 Eduardo Chappa * Copyright 2006-2009 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ #include "../pith/headers.h" #include "../pith/adrbklib.h" #include "../pith/abdlc.h" #include "../pith/addrbook.h" #include "../pith/addrstring.h" #include "../pith/state.h" #include "../pith/conf.h" #include "../pith/status.h" #include "../pith/remote.h" #include "../pith/tempfile.h" #include "../pith/bldaddr.h" #include "../pith/signal.h" #include "../pith/busy.h" #include "../pith/util.h" AddrScrState as; void (*pith_opt_save_and_restore)(int, SAVE_STATE_S *); /* * We don't want any end of line fixups to occur, so include "b" in DOS modes. */ #if defined(DOS) || defined(OS2) #define ADRBK_NAME "addrbook" #else #define ADRBK_NAME ".addressbook" #endif #define OPEN_WRITE_MODE (O_TRUNC|O_WRONLY|O_CREAT|O_BINARY) #ifndef MAXPATH #define MAXPATH 1000 /* Longest file path we can deal with */ #endif #define TABWIDTH 8 #define INDENTSTR " " #define INDENTXTRA " : " #define INDENT 3 /* length of INDENTSTR */ #define SLOP 3 static int writing; /* so we can give understandable error message */ AdrBk *edited_abook; static char empty[] = ""; jmp_buf jump_over_qsort; /* internal prototypes */ int copy_abook_to_tempfile(AdrBk *, char *, size_t); char *dir_containing(char *); char *get_next_abook_entry(FILE *, int); void strip_addr_string(char *, char **, char **); void adrbk_check_local_validity(AdrBk *, long); int write_single_abook_entry(AdrBk_Entry *, FILE *, int *, int *, int *, int *); char *backcompat_encoding_for_abook(char *, size_t, char *, size_t, char *); int percent_abook_saved(void); void exp_del_nth(EXPANDED_S *, a_c_arg_t); void exp_add_nth(EXPANDED_S *, a_c_arg_t); int cmp_ae_by_full_lists_last(const qsort_t *,const qsort_t *); int cmp_cntr_by_full_lists_last(const qsort_t *, const qsort_t *); int cmp_ae_by_full(const qsort_t *, const qsort_t *); int cmp_cntr_by_full(const qsort_t *, const qsort_t *); int cmp_ae_by_nick_lists_last(const qsort_t *,const qsort_t *); int cmp_cntr_by_nick_lists_last(const qsort_t *, const qsort_t *); int cmp_ae_by_nick(const qsort_t *, const qsort_t *); int cmp_cntr_by_nick(const qsort_t *, const qsort_t *); int cmp_addr(const qsort_t *, const qsort_t *); void sort_addr_list(char **); int build_abook_datastruct(AdrBk *, char *, size_t); AdrBk_Entry *init_ae(AdrBk *, AdrBk_Entry *, char *); adrbk_cntr_t count_abook_entries_on_disk(AdrBk *, a_c_arg_t *); AdrBk_Entry *adrbk_get_delae(AdrBk *, a_c_arg_t); void free_ae_parts(AdrBk_Entry *); void add_entry_to_trie(AdrBk_Trie **, char *, a_c_arg_t); int build_abook_tries(AdrBk *, char *); void repair_abook_tries(AdrBk *); adrbk_cntr_t lookup_nickname_in_trie(AdrBk *, char *); adrbk_cntr_t lookup_address_in_trie(AdrBk *, char *); adrbk_cntr_t lookup_in_abook_trie(AdrBk_Trie *, char *); void free_abook_trie(AdrBk_Trie **); adrbk_cntr_t re_sort_particular_entry(AdrBk *, a_c_arg_t); void move_ab_entry(AdrBk *, a_c_arg_t, a_c_arg_t); void insert_ab_entry(AdrBk *, a_c_arg_t, AdrBk_Entry *, int); void delete_ab_entry(AdrBk *, a_c_arg_t, int); void defvalue_ae(AdrBk_Entry *); /* * Open, read, and parse an address book. * * Args: pab -- the PerAddrBook structure * homedir -- the user's home directory if specified * warning -- put "why failed" message to user here * (provide space of at least 201 chars) * * If filename is NULL, the default will be used in the homedir * passed in. If homedir is NULL, the current dir will be used. * If filename is not NULL and is an absolute path, just the filename * will be used. Otherwise, it will be used relative to the homedir, or * to the current dir depending on whether or not homedir is NULL. * * Expected addressbook file format is: * \t\t\t\t * * The last two fields (\t\t) are optional. * * Lines that start with SPACE are continuation lines. Ends of lines are * treated as if they were spaces. The address field is either a single * address or a list of comma-separated addresses inside parentheses. * * Fields missing from the end of an entry are considered blank. * * Commas in the address field will cause problems, as will tabs in any * field. * * There may be some deleted entries in the addressbook that don't get * used. They look like regular entries except their nicknames start * with the string "#DELETED-YY/MM/DD#". */ AdrBk * adrbk_open(PerAddrBook *pab, char *homedir, char *warning, size_t warninglen, int sort_rule) { char path[MAXPATH], *filename; AdrBk *ab; int abook_validity_was_minusone = 0; filename = pab ? pab->filename : NULL; dprint((2, "- adrbk_open(%s) -\n", filename ? filename : "")); ab = (AdrBk *)fs_get(sizeof(AdrBk)); memset(ab, 0, sizeof(*ab)); ab->orig_filename = filename ? cpystr(filename) : NULL; if(pab->type & REMOTE_VIA_IMAP){ int try_cache; ab->type = Imap; if(!ab->orig_filename || *(ab->orig_filename) != '{'){ dprint((1, "adrbk_open: remote: filename=%s\n", ab->orig_filename ? ab->orig_filename : "NULL")); goto bail_out; } if(!(ab->rd = rd_new_remdata(RemImap, ab->orig_filename, REMOTE_ABOOK_SUBTYPE))){ dprint((1, "adrbk_open: remote: new_remdata failed: %s\n", ab->orig_filename ? ab->orig_filename : "NULL")); goto bail_out; } /* Transfer responsibility for the storage object */ ab->rd->so = pab->so; pab->so = NULL; try_cache = rd_read_metadata(ab->rd); if(ab->rd->lf) ab->filename = cpystr(ab->rd->lf); /* Transfer responsibility for removal of temp file */ if(ab->rd->flags & DEL_FILE){ ab->flags |= DEL_FILE; ab->rd->flags &= ~DEL_FILE; } if(pab->access == MaybeRorW){ if(ab->rd->read_status == 'R') pab->access = ab->rd->access = ReadOnly; else pab->access = ab->rd->access = ReadWrite; } else if(pab->access == ReadOnly){ /* * Pass on readonly-ness from being a global addrbook. * This should cause us to open the remote folder readonly, * avoiding error messages about readonly-ness. */ ab->rd->access = ReadOnly; } /* * The plan is to fetch addrbook data and copy into local file. * Then we open the local copy for reading. We use the IMAP STATUS * command to tell us if we need to update from the remote addrbook. * * If access is NoExists, that probably means we had trouble * opening the remote folder in the adrbk_access routine. * In that case we'll use a cached copy if we have one. */ if(pab->access != NoExists){ bootstrap_nocheck_policy: if(try_cache && ps_global->remote_abook_validity == -1 && !abook_validity_was_minusone) abook_validity_was_minusone++; else{ rd_check_remvalid(ab->rd, 1L); abook_validity_was_minusone = 0; } /* * If the cached info on this addrbook says it is readonly but * it looks like it's been fixed now, change it to readwrite. */ if(!(pab->type & GLOBAL) && ab->rd->read_status == 'R'){ /* * We go to this trouble since readonly addrbooks * are likely a mistake. They are usually supposed to be * readwrite. So we open it and check if it's been fixed. */ rd_check_readonly_access(ab->rd); if(ab->rd->read_status == 'W'){ pab->access = ab->rd->access = ReadWrite; ab->rd->flags |= REM_OUTOFDATE; } else pab->access = ab->rd->access = ReadOnly; } if(ab->rd->flags & REM_OUTOFDATE){ if(rd_update_local(ab->rd) != 0){ dprint((1, "adrbk_open: remote: rd_update_local failed\n")); /* * Don't give up altogether. We still may be * able to use a cached copy. */ } else{ dprint((7, "%s: copied remote to local (%ld)\n", ab->rd->rn ? ab->rd->rn : "?", (long)ab->rd->last_use)); } } if(pab->access == ReadWrite) ab->rd->flags |= DO_REMTRIM; } /* If we couldn't get to remote folder, try using the cached copy */ if(pab->access == NoExists || ab->rd->flags & REM_OUTOFDATE){ if(try_cache){ pab->access = ab->rd->access = ReadOnly; ab->rd->flags |= USE_OLD_CACHE; q_status_message(SM_ORDER, 3, 4, _("Can't contact remote address book server, using cached copy")); dprint((2, "Can't open remote addrbook %s, using local cached copy %s readonly\n", ab->rd->rn ? ab->rd->rn : "?", ab->rd->lf ? ab->rd->lf : "?")); } else goto bail_out; } } else{ ab->type = Local; /*------------ figure out and save name of file to open ---------*/ if(filename == NULL){ if(homedir != NULL){ build_path(path, homedir, ADRBK_NAME, sizeof(path)); ab->filename = cpystr(path); } else ab->filename = cpystr(ADRBK_NAME); } else{ if(is_absolute_path(filename)){ ab->filename = cpystr(filename); } else{ if(homedir != NULL){ build_path(path, homedir, filename, sizeof(path)); ab->filename = cpystr(path); } else ab->filename = cpystr(filename); } } } if(ab->filename && ab->filename[0]){ char buf[MAXPATH]; strncpy(buf, ab->filename, sizeof(buf)-4); buf[sizeof(buf)-4] = '\0'; /* * Our_filecopy is used in _WINDOWS to allow * multiple pines to update the address book. The problem is * that if a file is open it can't be deleted, so we need to keep * the main filename closed most of the time. * In Unix, our_filecopy just points to filename. */ #ifdef _WINDOWS /* * If we can't write in the same directory as filename is in, put * the copies in /tmp instead. */ if(!(ab->our_filecopy = tempfile_in_same_dir(ab->filename, "a3", NULL))) ab->our_filecopy = temp_nam(NULL, "a3"); #endif /* _WINDOWS */ /* * We don't need the copies on Unix because we can rename/delete * open files. Turn the feature off by making the copies point to * the originals. */ if(!ab->our_filecopy) ab->our_filecopy = ab->filename; } else{ dprint((1, "adrbk_open: ab->filename is NULL???\n")); goto bail_out; } /* * We're going to make our own copy of the address book file so that * we won't conflict with other instances of pine trying to change it. * In particular, on Windows the address book file cannot be deleted * or renamed into if it is open in another process. */ ab->flags |= FILE_OUTOFDATE; if(copy_abook_to_tempfile(ab, warning, warninglen) < 0){ dprint((1, "adrbk_open: copy_file failed\n")); if(abook_validity_was_minusone){ /* * The file copy failed when it shouldn't have. If the user has * remote_abook_validity == -1 then we'll go back and try to * do the validity check in case that can get us the file we * need to copy. Without the validity check first time we won't * contact the imap server. */ dprint((1, "adrbk_open: trying to bootstrap\n")); if(ab->our_filecopy){ if(ab->our_filecopy != ab->filename){ our_unlink(ab->our_filecopy); fs_give((void **)&ab->our_filecopy); } ab->our_filecopy = NULL; } goto bootstrap_nocheck_policy; } goto bail_out; } if(!ab->fp) goto bail_out; ab->sort_rule = sort_rule; if(pab->access == ReadOnly) ab->sort_rule = AB_SORT_RULE_NONE; if(ab){ /* allocate header for expanded lists list */ ab->exp = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S)); /* first real element is NULL */ ab->exp->next = (EXPANDED_S *)NULL; /* allocate header for checked entries list */ ab->checks = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S)); /* first real element is NULL */ ab->checks->next = (EXPANDED_S *)NULL; /* allocate header for selected entries list */ ab->selects = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S)); /* first real element is NULL */ ab->selects->next = (EXPANDED_S *)NULL; return(ab); } bail_out: dprint((2, "adrbk_open: bailing: filenames=%s %s %s fp=%s\n", ab->orig_filename ? ab->orig_filename : "NULL", ab->filename ? ab->filename : "NULL", ab->our_filecopy ? ab->our_filecopy : "NULL", ab->fp ? "open" : "NULL")); if(ab->rd){ ab->rd->flags &= ~DO_REMTRIM; rd_close_remdata(&ab->rd); } if(ab->fp) (void)fclose(ab->fp); if(ab->orig_filename) fs_give((void **) &ab->orig_filename); if(ab->our_filecopy && ab->our_filecopy != ab->filename){ our_unlink(ab->our_filecopy); fs_give((void **) &ab->our_filecopy); } if(ab->filename) fs_give((void **) &ab->filename); if(pab->so){ so_give(&(pab->so)); pab->so = NULL; } fs_give((void **) &ab); return NULL; } /* * Copy the address book file to the temporary session copy. Also copy * the hashfile. Any of these files which don't exist will be created. * * Returns 0 success * -1 failure */ int copy_abook_to_tempfile(AdrBk *ab, char *warning, size_t warninglen) { int got_it, fd, c, ret = -1, we_cancel = 0; FILE *fp_read = (FILE *)NULL, *fp_write = (FILE *)NULL; char *lc; time_t mtime; dprint((3, "copy_file(%s) -\n", (ab && ab->filename) ? ab->filename : "")); if(!ab || !ab->filename || !ab->filename[0]) goto get_out; if(!(ab->flags & FILE_OUTOFDATE)) return(0); /* open filename for reading */ fp_read = our_fopen(ab->filename, "rb"); if(fp_read == NULL){ /* * filename probably doesn't exist so we try to create it */ /* don't want to create in these cases, should already be there */ if(ab->type == Imap){ if(warning){ if(ab->type == Imap){ snprintf(warning, warninglen, /* TRANSLATORS: A temporary file for the address book can't be opened. */ _("Temp addrbook file can't be opened: %s"), ab->filename); warning[warninglen-1] = '\0'; } else{ strncpy(warning, _("Address book doesn't exist"), warninglen); warning[warninglen-1] = '\0'; } } goto get_out; } q_status_message1(SM_INFO, 0, 3, _("Address book %.200s doesn't exist, creating"), (lc=last_cmpnt(ab->filename)) ? lc : ab->filename); dprint((2, "Address book %s doesn't exist, creating\n", ab->filename ? ab->filename : "?")); /* * Just use user's umask for permissions. Mode is "w" so the create * will happen. We close it right after creating and open it in * read mode again later. */ fp_read = our_fopen(ab->filename, "wb"); /* create */ if(fp_read == NULL || fclose(fp_read) == EOF || (fp_read = our_fopen(ab->filename, "rb")) == NULL){ /*--- Create failed, bail out ---*/ if(warning){ strncpy(warning, error_description(errno), warninglen); warning[warninglen-1] = '\0'; } dprint((2, "create failed: %s\n", error_description(errno))); goto get_out; } } /* record new change date of addrbook file */ ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename); ab->last_local_valid_chk = get_adj_time(); /* now there is an ab->filename and we have it open for reading */ got_it = 0; /* copy ab->filename to ab->our_filecopy, preserving mtime */ if(ab->filename != ab->our_filecopy){ struct stat sbuf; struct utimbuf times; int valid_stat = 0; dprint((7, "Before abook copies\n")); if((fd = our_open(ab->our_filecopy, OPEN_WRITE_MODE, 0600)) < 0) goto get_out; fp_write = fdopen(fd, "wb"); rewind(fp_read); if(fstat(fileno(fp_read), &sbuf)){ q_status_message1(SM_INFO, 0, 3, "Error: can't stat addrbook \"%.200s\"", (lc=last_cmpnt(ab->filename)) ? lc : ab->filename); dprint((2, "Error: can't stat addrbook \"%s\"\n", ab->filename ? ab->filename : "?")); } else{ valid_stat++; times.actime = sbuf.st_atime; times.modtime = sbuf.st_mtime; } while((c = getc(fp_read)) != EOF) if(putc(c, fp_write) == EOF) goto get_out; (void)fclose(fp_write); fp_write = (FILE *)NULL; if(valid_stat && our_utime(ab->our_filecopy, ×)){ q_status_message1(SM_INFO, 0, 3, "Error: can't set mtime for \"%.200s\"", (lc=last_cmpnt(ab->filename)) ? lc : ab->our_filecopy); dprint((2, "Error: can't set mtime for \"%s\"\n", ab->our_filecopy ? ab->our_filecopy : "?")); } (void)fclose(fp_read); fp_read = (FILE *)NULL; if(!(ab->fp = our_fopen(ab->our_filecopy, "rb"))) goto get_out; dprint((7, "After abook file copy\n")); } else{ /* already open to the right file */ ab->fp = fp_read; fp_read = (FILE *)NULL; } /* * Now we've copied filename to our_filecopy. * Operate on the copy now. Ab->fp is open readonly on * our_filecopy. */ got_it = 0; mtime = get_adj_name_file_mtime(ab->our_filecopy); we_cancel = busy_cue(NULL, NULL, 1); if(build_abook_datastruct(ab, (warning && !*warning) ? warning : NULL, warninglen)){ if(we_cancel) cancel_busy_cue(-1); dprint((2, "failed in build_abook_datastruct\n")); goto get_out; } if(ab->arr && build_abook_tries(ab, (warning && !*warning) ? warning : NULL)){ if(we_cancel) cancel_busy_cue(-1); dprint((2, "failed in build_abook_tries\n")); goto get_out; } if(we_cancel) cancel_busy_cue(-1); ab->flags &= ~FILE_OUTOFDATE; /* turn off out of date flag */ ret = 0; get_out: if(fp_read) (void)fclose(fp_read); if(fp_write) (void)fclose(fp_write); if(ret < 0 && ab){ if(ab->our_filecopy && ab->our_filecopy != ab->filename) our_unlink(ab->our_filecopy); } return(ret); } /* * Returns an allocated copy of the directory which contains filename. */ char * dir_containing(char *filename) { char dir[MAXPATH+1]; char *dirp = NULL; if(filename){ char *lc; if((lc = last_cmpnt(filename)) != NULL){ int to_copy; to_copy = (lc - filename > 1) ? (lc - filename - 1) : 1; strncpy(dir, filename, MIN(to_copy, sizeof(dir)-1)); dir[MIN(to_copy, sizeof(dir)-1)] = '\0'; } else{ dir[0] = '.'; dir[1] = '\0'; } dirp = cpystr(dir); } return(dirp); } /* * Checks whether or not the addrbook is sorted correctly according to * the SortType. Returns 1 if is sorted correctly, 0 otherwise. */ int adrbk_is_in_sort_order(AdrBk *ab, int be_quiet) { adrbk_cntr_t entry; AdrBk_Entry *ae, *ae_prev; int (*cmp_func)(const qsort_t *, const qsort_t *); int we_cancel = 0; dprint((9, "- adrbk_is_in_sort_order -\n")); if(!ab) return 0; if(ab->sort_rule == AB_SORT_RULE_NONE || ab->count < 2) return 1; cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ? cmp_ae_by_full_lists_last : (ab->sort_rule == AB_SORT_RULE_FULL) ? cmp_ae_by_full : (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ? cmp_ae_by_nick_lists_last : /* (ab->sort_rule == AB_SORT_RULE_NICK) */ cmp_ae_by_nick; ae_prev = adrbk_get_ae(ab, (a_c_arg_t) 0); if(!be_quiet) we_cancel = busy_cue(NULL, NULL, 1); for(entry = 1, ae = adrbk_get_ae(ab, (a_c_arg_t) entry); ae != (AdrBk_Entry *)NULL; ae = adrbk_get_ae(ab, (a_c_arg_t) (++entry))){ if((*cmp_func)((qsort_t *)&ae_prev, (qsort_t *)&ae) > 0){ if(we_cancel) cancel_busy_cue(-1); dprint((9, "- adrbk_is_in_sort_order : no (entry %ld) -\n", (long) entry)); return 0; } ae_prev = ae; } if(we_cancel) cancel_busy_cue(-1); dprint((9, "- adrbk_is_in_sort_order : yes -\n")); return 1; } /* * Look through the ondisk address book and count the number of entries. * * Args ab -- address book pointer * deleted -- pointer to location to return number of deleted entries * warning -- place to put warning * * Returns number of non-deleted entries. */ adrbk_cntr_t count_abook_entries_on_disk(AdrBk *ab, a_c_arg_t *deleted) { FILE *fp_in; char *nickname; adrbk_cntr_t count = 0; adrbk_cntr_t deleted_count = 0; int rew = 1; char nickbuf[50]; if(!ab || !ab->fp) return -1; fp_in = ab->fp; while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){ rew = 0; strncpy(nickbuf, nickname, sizeof(nickbuf)); nickbuf[sizeof(nickbuf)-1] = '\0'; fs_give((void **) &nickname); if(strncmp(nickbuf, DELETED, DELETED_LEN) == 0 && isdigit((unsigned char)nickbuf[DELETED_LEN]) && isdigit((unsigned char)nickbuf[DELETED_LEN+1]) && nickbuf[DELETED_LEN+2] == '/' && isdigit((unsigned char)nickbuf[DELETED_LEN+3]) && isdigit((unsigned char)nickbuf[DELETED_LEN+4]) && nickbuf[DELETED_LEN+5] == '/' && isdigit((unsigned char)nickbuf[DELETED_LEN+6]) && isdigit((unsigned char)nickbuf[DELETED_LEN+7]) && nickbuf[DELETED_LEN+8] == '#'){ deleted_count++; continue; } count++; } if(deleted) *deleted = (a_c_arg_t) deleted_count; return(count); } /* * Builds the data structures used with the address book which is * simply an array of entries. */ int build_abook_datastruct(AdrBk *ab, char *warning, size_t warninglen) { FILE *fp_in; char *nickname; char *lc; a_c_arg_t count, deleted; adrbk_cntr_t used = 0, delused = 0; int max_nick = 0, max_full = 0, full_two = 0, full_three = 0, max_fcc = 0, fcc_two = 0, fcc_three = 0, max_addr = 0, addr_two = 0, addr_three = 0, this_nick_width, this_full_width, this_addr_width, this_fcc_width; WIDTH_INFO_S *widths; int rew = 1, is_deleted; AdrBk_Entry *ae; dprint((9, "- build_abook_datastruct -\n")); if(!ab || !ab->fp) return -1; errno = 0; fp_in = ab->fp; /* * If we used a list instead of an array to store the entries we * could avoid this pass through the file to count the entries, which * we use to allocate the array. * * Since we use entry_nums a lot to access address book entries it is * convenient to have an array for quick access, so we'll probably * leave this for now. */ count = count_abook_entries_on_disk(ab, &deleted); if(count < 0) return -1; ab->count = (adrbk_cntr_t) count; ab->del_count = (adrbk_cntr_t) deleted; if(count > 0){ ab->arr = (AdrBk_Entry *) fs_get(count * sizeof(AdrBk_Entry)); memset(ab->arr, 0, count * sizeof(AdrBk_Entry)); } if(deleted > 0){ ab->del = (AdrBk_Entry *) fs_get(deleted * sizeof(AdrBk_Entry)); memset(ab->del, 0, deleted * sizeof(AdrBk_Entry)); } while((nickname = get_next_abook_entry(fp_in, rew)) != NULL){ ae = NULL; rew = 0; is_deleted = 0; if(strncmp(nickname, DELETED, DELETED_LEN) == 0 && isdigit((unsigned char)nickname[DELETED_LEN]) && isdigit((unsigned char)nickname[DELETED_LEN+1]) && nickname[DELETED_LEN+2] == '/' && isdigit((unsigned char)nickname[DELETED_LEN+3]) && isdigit((unsigned char)nickname[DELETED_LEN+4]) && nickname[DELETED_LEN+5] == '/' && isdigit((unsigned char)nickname[DELETED_LEN+6]) && isdigit((unsigned char)nickname[DELETED_LEN+7]) && nickname[DELETED_LEN+8] == '#'){ is_deleted++; } ALARM_BLIP(); if(!is_deleted && (long) used > MAX_ADRBK_SIZE){ q_status_message2(SM_ORDER | SM_DING, 4, 5, "Max addrbook size is %.200s, %.200s too large, giving up", long2string(MAX_ADRBK_SIZE), (lc=last_cmpnt(ab->filename)) ? lc : ab->filename); dprint((1, "build_ondisk: used=%ld > %s\n", (long) used, long2string(MAX_ADRBK_SIZE))); goto io_err; } if(is_deleted){ if(delused < ab->del_count) ae = &ab->del[delused++]; } else{ if(used < ab->count) ae = &ab->arr[used++]; } if(ae) init_ae(ab, ae, nickname); fs_give((void **) &nickname); if(!ae || is_deleted) continue; /* * We're calculating the widths as we read in the data. * We could just go with some default widths to save time. */ this_nick_width = 0; this_full_width = 0; this_addr_width = 0; this_fcc_width = 0; if(ae->nickname) this_nick_width = (int) utf8_width(ae->nickname); if(ae->fullname) this_full_width = (int) utf8_width(ae->fullname); if(ae->tag == Single){ if(ae->addr.addr) this_addr_width = (int) utf8_width(ae->addr.addr); } else{ char **a2; this_addr_width = 0; for(a2 = ae->addr.list; *a2 != NULL; a2++) this_addr_width = MAX(this_addr_width, (int) utf8_width(*a2)); } if(ae->fcc) this_fcc_width = (int) utf8_width(ae->fcc); max_nick = MAX(max_nick, this_nick_width); if(this_full_width > max_full){ full_three = full_two; full_two = max_full; max_full = this_full_width; } else if(this_full_width > full_two){ full_three = full_two; full_two = this_full_width; } else if(this_full_width > full_three){ full_three = this_full_width; } if(this_addr_width > max_addr){ addr_three = addr_two; addr_two = max_addr; max_addr = this_addr_width; } else if(this_addr_width > addr_two){ addr_three = addr_two; addr_two = this_addr_width; } else if(this_addr_width > addr_three){ addr_three = this_addr_width; } if(this_fcc_width > max_fcc){ fcc_three = fcc_two; fcc_two = max_fcc; max_fcc = this_fcc_width; } else if(this_fcc_width > fcc_two){ fcc_three = fcc_two; fcc_two = this_fcc_width; } else if(this_fcc_width > fcc_three){ fcc_three = this_fcc_width; } } widths = &ab->widths; widths->max_nickname_width = MIN(max_nick, 99); widths->max_fullname_width = MIN(max_full, 99); widths->max_addrfield_width = MIN(max_addr, 99); widths->max_fccfield_width = MIN(max_fcc, 99); widths->third_biggest_fullname_width = MIN(full_three, 99); widths->third_biggest_addrfield_width = MIN(addr_three, 99); widths->third_biggest_fccfield_width = MIN(fcc_three, 99); dprint((9, "- build_abook_datastruct done -\n")); return 0; io_err: if(warning && errno != 0){ strncpy(warning, error_description(errno), warninglen); warning[warninglen-1] = '\0'; } dprint((1, "build_ondisk: io_err: %s\n", error_description(errno))); return -1; } /* * Builds the trees used for nickname and address lookups. */ int build_abook_tries(AdrBk *ab, char *warning) { adrbk_cntr_t entry_num; AdrBk_Entry *ae; int we_cancel = 0; if(!ab) return -1; dprint((9, "- build_abook_tries(%s) -\n", ab->filename)); if(ab->nick_trie) free_abook_trie(&ab->nick_trie); if(ab->addr_trie) free_abook_trie(&ab->addr_trie); if(ab->full_trie) free_abook_trie(&ab->full_trie); if(ab->revfull_trie) free_abook_trie(&ab->revfull_trie); /* * Go through addrbook entries and add each to the tries it * belongs in. */ for(entry_num = 0; entry_num < ab->count; entry_num++){ ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num); if(ae){ /* every nickname in the nick trie */ if(ae->nickname && ae->nickname[0]) add_entry_to_trie(&ab->nick_trie, ae->nickname, (a_c_arg_t) entry_num); if(ae->fullname && ae->fullname[0]){ char *reverse = NULL; char *forward = NULL; char *comma = NULL; /* * We have some fullnames stored as Last, First. Put both in. */ if(ae->fullname[0] != '"' && (comma=strindex(ae->fullname, ',')) != NULL && comma - ae->fullname > 0){ forward = adrbk_formatname(ae->fullname, NULL, NULL); if(forward && forward[0]){ *comma = '\0'; reverse = cpystr(ae->fullname); *comma = ','; } else{ if(forward) fs_give((void **) &forward); forward = ae->fullname; } } else forward = ae->fullname; if(forward){ char *addthis; /* * Make this add not only the full name as is (forward) but * also add the middle name and last name (names after spaces). * so that they'll be found. */ for(addthis=forward; addthis && (*addthis); addthis = strindex(addthis, ' ')){ while(*addthis == ' ') addthis++; if(*addthis) add_entry_to_trie(&ab->full_trie, addthis, (a_c_arg_t) entry_num); } } if(reverse){ add_entry_to_trie(&ab->revfull_trie, reverse, (a_c_arg_t) entry_num); fs_give((void **) &reverse); } if(forward && forward != ae->fullname) fs_give((void **) &forward); } if(ae->tag == Single && ae->addr.addr && ae->addr.addr[0]){ char buf[1000]; char *tmp_a_string, *simple_addr = NULL; ADDRESS *addr = NULL; char *fakedomain = "@"; /* * Isolate the actual address out of ae. */ tmp_a_string = cpystr(ae->addr.addr); rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain); if(tmp_a_string) fs_give((void **) &tmp_a_string); if(addr){ if(addr->mailbox && addr->host && !(addr->host[0] == '@' && addr->host[1] == '\0')) simple_addr = simple_addr_string(addr, buf, sizeof(buf)); /* * If the fullname wasn't set in the addrbook entry there * may still be one that is part of the address. We need * to be careful because we may be in the middle of opening * the address book right now. Don't call something like * our_build_address because it will probably re-open this * same addrbook infinitely. */ if(!(ae->fullname && ae->fullname[0]) && addr->personal && addr->personal[0]) add_entry_to_trie(&ab->full_trie, addr->personal, (a_c_arg_t) entry_num); mail_free_address(&addr); } if(simple_addr) add_entry_to_trie(&ab->addr_trie, simple_addr, (a_c_arg_t) entry_num); } } } dprint((9, "- build_abook_tries done -\n")); if(we_cancel) cancel_busy_cue(-1); return 0; } void add_entry_to_trie(AdrBk_Trie **head, char *str, a_c_arg_t entry_num) { AdrBk_Trie *temp, *trail; char *addthis, *p, buf[1000]; if(!head || !str || !*str) return; /* add as lower case */ for(p = str; *p && ((*p & 0x80) || !isupper((unsigned char) *p)); p++) ; if(*p){ strncpy(buf, str, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; for(p = buf; *p; p++) if(!(*p & 0x80) && isupper((unsigned char) *p)) *p = tolower(*p); addthis = buf; } else addthis = str; temp = trail = (*head); /* * Find way down the trie, adding missing nodes as we go. */ for(p = addthis; *p;){ if(temp == NULL){ temp = (AdrBk_Trie *) fs_get(sizeof(*temp)); memset(temp, 0, sizeof(*temp)); temp->value = *p; temp->entrynum = NO_NEXT; if(*head == NULL) *head = temp; else trail->down = temp; } else{ while((temp != NULL) && (temp->value != *p)){ trail = temp; temp = temp->right; } /* wasn't there, add new node */ if(temp == NULL){ temp = (AdrBk_Trie *) fs_get(sizeof(*temp)); memset(temp, 0, sizeof(*temp)); temp->value = *p; temp->entrynum = NO_NEXT; trail->right = temp; } } if(*(++p)){ trail = temp; temp = temp->down; } } /* * If entrynum is already filled in there must be an entry with * the same nickname earlier in the abook. Use that earlier entry. */ if(temp != NULL && temp->entrynum == NO_NEXT) temp->entrynum = (adrbk_cntr_t) entry_num; } /* * Returns entry_num of first entry with this nickname, else NO_NEXT. */ adrbk_cntr_t lookup_nickname_in_trie(AdrBk *ab, char *nickname) { if(!ab || !nickname || !ab->nick_trie) return(-1L); return(lookup_in_abook_trie(ab->nick_trie, nickname)); } /* * Returns entry_num of first entry with this address, else NO_NEXT. */ adrbk_cntr_t lookup_address_in_trie(AdrBk *ab, char *address) { dprint((9, "lookup_address_in_trie: %s\n", ab ? (ab->addr_trie ? (address ? address : "?") : "null addr_trie") : "null ab")); if(!ab || !address || !ab->addr_trie) return(-1L); return(lookup_in_abook_trie(ab->addr_trie, address)); } adrbk_cntr_t lookup_in_abook_trie(AdrBk_Trie *t, char *str) { char *p, *lookthisup; char buf[1000]; adrbk_cntr_t ret = NO_NEXT; if(!t || !str) return(ret); /* make lookup case independent */ for(p = str; *p && !(*p & 0x80) && islower((unsigned char) *p); p++) ; if(*p){ strncpy(buf, str, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; for(p = buf; *p; p++) if(!(*p & 0x80) && isupper((unsigned char) *p)) *p = tolower(*p); lookthisup = buf; } else lookthisup = str; p = lookthisup; /* * We usually return out from inside the loop (unless str == ""). */ while(*p){ /* search for character at this level */ while(t->value != *p){ if(t->right == NULL) return(ret); /* no match */ t = t->right; } if(*++p == '\0') /* end of str, a match */ return(t->entrynum); /* need to go down to match next character */ if(t->down == NULL) /* no match */ return(ret); t = t->down; } return(ret); } void free_abook_trie(AdrBk_Trie **trie) { if(trie){ if(*trie){ free_abook_trie(&(*trie)->down); free_abook_trie(&(*trie)->right); fs_give((void **) trie); } } } /* * Returns pointer to start of next address book entry from disk file. * The return will be in raw form from the file with newlines still * embedded. Or NULL at end of file. * * If rew is set, rewind the file and start over at beginning. */ char * get_next_abook_entry(FILE *fp, int rew) { char *returned_lines = NULL, *p; char line[1024]; static int will_be_done_next_time = 0; static long next_nickname_offset = 0L; size_t lsize; long offset, saved_offset; long len = 0L; #define CHUNKSIZE 500 lsize = sizeof(line); if(rew){ will_be_done_next_time = 0; rewind(fp); /* skip leading (bogus) continuation lines */ do{ offset = ftell(fp); line[0] = '\0'; line[lsize-2] = '\0'; p = fgets(line, lsize, fp); if(p == NULL) return(NULL); /* line is too long to fit, read the rest and discard */ while(line[lsize-2] != '\0' && line[lsize-2] != '\n' && p != NULL){ /* get next lsize-1 characters, leaving line[0] */ line[lsize-2] = '\0'; p = fgets(line+1, lsize-1, fp); } }while(line[0] == SPACE); /* offset is start of first good line now */ next_nickname_offset = offset; } if(will_be_done_next_time) return(NULL); /* we set this up in rew==1 case or on previous call to this routine */ offset = next_nickname_offset; /* * The rest is working on finding the start of the next entry so * skip continuation lines */ do{ next_nickname_offset = ftell(fp); line[0] = '\0'; line[lsize-2] = '\0'; p = fgets(line, lsize, fp); /* line is too long to fit, read the rest and discard */ while(line[lsize-2] != '\0' && line[lsize-2] != '\n' && p != NULL){ /* get next lsize-1 characters, leaving line[0] */ line[lsize-2] = '\0'; p = fgets(line+1, lsize-1, fp); } }while(line[0] == SPACE); /* next_nickname_offset is start of next entry now */ if(!line[0]) will_be_done_next_time = 1; len = next_nickname_offset - offset; returned_lines = (char *) fs_get((len + 1) * sizeof(char)); saved_offset = ftell(fp); if(fseek(fp, offset, 0)){ dprint((2, "get_next_ab_entry: trouble fseeking\n")); len = 0; fs_give((void **) &returned_lines); } else{ if(fread(returned_lines, sizeof(char), (unsigned) len, fp) != len){ dprint((2, "get_next_ab_entry: trouble freading\n")); len = 0; fs_give((void **) &returned_lines); } } if(fseek(fp, saved_offset, 0)){ dprint((2, "get_next_ab_entry: trouble fseeking to saved_offset\n")); len = 0; fs_give((void **) &returned_lines); } if(returned_lines) returned_lines[len] = '\0'; return(returned_lines); } /* * Returns a pointer to the start of the mailbox@host part of this * address string, and a pointer to the end + 1. The caller can then * replace the end char with \0 and call the hash function, then put * back the end char. Start_addr and end_addr are assumed to be non-null. */ void strip_addr_string(char *addrstr, char **start_addr, char **end_addr) { register char *q; int in_quotes = 0, in_comment = 0; char prev_char = '\0'; if(!addrstr || !*addrstr){ *start_addr = NULL; *end_addr = NULL; return; } *start_addr = addrstr; for(q = addrstr; *q; q++){ switch(*q){ case '<': if(!in_quotes && !in_comment){ if(*++q){ *start_addr = q; /* skip to > */ while(*q && *q != '>') q++; /* found > */ if(*q){ *end_addr = q; return; } else q--; } } break; case LPAREN: if(!in_quotes && !in_comment) in_comment = 1; break; case RPAREN: if(in_comment && prev_char != BSLASH) in_comment = 0; break; case QUOTE: if(in_quotes && prev_char != BSLASH) in_quotes = 0; else if(!in_quotes && !in_comment) in_quotes = 1; break; default: break; } prev_char = *q; } *end_addr = q; } /* * Fill in the passed in ae pointer by parsing the str that is passed. * * Args ab -- * ae -- pointer we want to fill in. The individual members of * the ae struct will be allocated here * str -- the string from the on-disk address book file for this entry * * Returns a pointer to ae or NULL if there are problems. */ AdrBk_Entry * init_ae(AdrBk *ab, AdrBk_Entry *a, char *str) { char *p; char *addrfield = (char *) NULL; char *addrfield_end; char *nickname, *fullname, *fcc, *extra; if(!ab){ dprint((2, "init_ae: found trouble: NULL ab\n")); return((AdrBk_Entry *) NULL); } defvalue_ae(a); p = str; REPLACE_NEWLINES_WITH_SPACE(p); nickname = p; SKIP_TO_TAB(p); if(!*p){ RM_END_SPACE(nickname, p); a->nickname = cpystr(nickname); } else{ *p = '\0'; RM_END_SPACE(nickname, p); a->nickname = cpystr(nickname); p++; SKIP_SPACE(p); fullname = p; SKIP_TO_TAB(p); if(!*p){ RM_END_SPACE(fullname, p); a->fullname = cpystr(fullname); } else{ *p = '\0'; RM_END_SPACE(fullname, p); a->fullname = cpystr(fullname); p++; SKIP_SPACE(p); addrfield = p; SKIP_TO_TAB(p); if(!*p){ RM_END_SPACE(addrfield, p); } else{ *p = '\0'; RM_END_SPACE(addrfield, p); p++; SKIP_SPACE(p); fcc = p; SKIP_TO_TAB(p); if(!*p){ RM_END_SPACE(fcc, p); a->fcc = cpystr(fcc); } else{ char *src, *dst; *p = '\0'; RM_END_SPACE(fcc, p); a->fcc = cpystr(fcc); p++; SKIP_SPACE(p); extra = p; p = extra + strlen(extra); RM_END_SPACE(extra, p); /* * When we wrap long comments we insert an extra colon * in the wrap so we can spot it and take it back out. * Pretty much a hack since we thought of it a long * time after designing it, but it eliminates the limit * on length of comments. Here we are looking for * : or * : and replacing it with . * There could have been a single \n or \r\n, so that * is why we check for 2 or 3 spaces before the colon. * (This was another mistake.) */ dst = src = extra; while(*src != '\0'){ if(*src == SPACE && *(src+1) == SPACE && *(src+2) == ':' && *(src+3) == SPACE){ /* * If there was an extra space because of the * CRLF (instead of LF) then we already put * a SP in dst last time through the loop * and don't need to add another. */ if(src == extra || *(src-1) != SPACE) *dst++ = *src; src += 4; } else *dst++ = *src++; } *dst = '\0'; a->extra = cpystr(extra); } } } } /* decode and convert to UTF-8 if we need to */ convert_possibly_encoded_str_to_utf8(&a->nickname); convert_possibly_encoded_str_to_utf8(&a->fullname); convert_possibly_encoded_str_to_utf8(&a->fcc); convert_possibly_encoded_str_to_utf8(&a->extra); /* parse addrfield */ if(addrfield){ if(*addrfield == '('){ /* it's a list */ a->tag = List; p = addrfield; addrfield_end = p + strlen(p); /* * Get rid of the parens. * If this isn't true the input file is messed up. */ if(p[strlen(p)-1] == ')'){ char **ll; p[strlen(p)-1] = '\0'; p++; a->addr.list = parse_addrlist(p); for(ll = a->addr.list; ll && *ll; ll++) convert_possibly_encoded_str_to_utf8(ll); } else{ /* put back what was there to start with */ *addrfield_end = ')'; a->addr.list = (char **)fs_get(sizeof(char *) * 2); a->addr.list[0] = cpystr(addrfield); a->addr.list[1] = NULL; dprint((2, "parsing error reading addressbook: missing right paren: %s\n", addrfield ? addrfield : "?")); } } else{ /* A plain, single address */ a->tag = Single; a->addr.addr = cpystr(addrfield); convert_possibly_encoded_str_to_utf8(&a->addr.addr); } } else{ /* * If no addrfield, assume an empty Single. */ a->addr.addr = cpystr(""); a->tag = Single; } return(a); } /* * Return the size of the address book */ adrbk_cntr_t adrbk_count(AdrBk *ab) { return(ab ? ab->count : (adrbk_cntr_t) 0); } /* * Return a pointer to the ae that has index number "entry_num". */ AdrBk_Entry * adrbk_get_ae(AdrBk *ab, a_c_arg_t entry_num) { if(!ab || entry_num >= (a_c_arg_t) ab->count) return((AdrBk_Entry *) NULL); return(&ab->arr[entry_num]); } /* * Return a pointer to the deleted ae that has index number "entry_num". */ AdrBk_Entry * adrbk_get_delae(AdrBk *ab, a_c_arg_t entry_num) { if(!ab || entry_num >= (a_c_arg_t) ab->del_count) return((AdrBk_Entry *) NULL); return(&ab->del[entry_num]); } /* * Look up an entry in the address book given a nickname * * Args: ab -- the address book * nickname -- nickname to match * entry_num -- if matched, return entry_num of match here * * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found. * * Lookups usually need to be recursive in case the address * book references itself. This is left to the next level up. * adrbk_clearrefs() is provided to clear all the reference tags in * the address book for loop detection. * When there are duplicates of the same nickname we return the first. * This can only happen if addrbook was edited externally. */ AdrBk_Entry * adrbk_lookup_by_nick(AdrBk *ab, char *nickname, adrbk_cntr_t *entry_num) { adrbk_cntr_t num; AdrBk_Entry *ae; dprint((5, "- adrbk_lookup_by_nick(%s) (in %s) -\n", nickname ? nickname : "?", (ab && ab->filename) ? ab->filename : "?")); if(!ab || !nickname || !nickname[0]) return NULL; num = lookup_nickname_in_trie(ab, nickname); if(num != NO_NEXT){ ae = adrbk_get_ae(ab, (a_c_arg_t) num); if(entry_num && ae) *entry_num = num; return(ae); } else return((AdrBk_Entry *) NULL); } /* * Look up an entry in the address book given an address * * Args: ab -- the address book * address -- address to match * entry_num -- if matched, return entry_num of match here * * Result: A pointer to an AdrBk_Entry is returned, or NULL if not found. * * Note: When there are multiple occurrences of an address in an addressbook, * which there will be if more than one nickname points to same address, then * we want this to match the first occurrence so that the fcc you get will * be predictable. */ AdrBk_Entry * adrbk_lookup_by_addr(AdrBk *ab, char *address, adrbk_cntr_t *entry_num) { adrbk_cntr_t num; AdrBk_Entry *ae; dprint((5, "- adrbk_lookup_by_addr(%s) (in %s) -\n", address ? address : "?", (ab && ab->filename) ? ab->filename : "?")); if(!ab || !address || !address[0]) return((AdrBk_Entry *)NULL); num = lookup_address_in_trie(ab, address); if(num != NO_NEXT){ ae = adrbk_get_ae(ab, (a_c_arg_t) num); if(entry_num && ae) *entry_num = num; return(ae); } else return((AdrBk_Entry *)NULL); } /* * Format a full name. * * Args: fullname -- full name out of address book for formatting * first -- Return a pointer to first name here. * last -- Return a pointer to last name here. * * Result: Returns pointer to name formatted for a mail header. Space is * allocated here and should be freed by caller. * * We need this because we store full names as Last, First. * If the name has no comma, then no change is made. * Otherwise the text before the first comma is moved to the end and * the comma is deleted. * * Last and first have to be freed by caller. */ char * adrbk_formatname(char *fullname, char **first, char **last) { char *comma; char *new_name; if(first) *first = NULL; if(last) *last = NULL; /* * There is an assumption that the fullname is a UTF-8 string. */ if(fullname[0] != '"' && (comma = strindex(fullname, ',')) != NULL){ size_t l; int last_name_len = comma - fullname; comma++; while(*comma && isspace((unsigned char)*comma)) comma++; if(first) *first = cpystr(comma); if(last){ *last = (char *)fs_get((last_name_len + 1) * sizeof(char)); strncpy(*last, fullname, last_name_len); (*last)[last_name_len] = '\0'; } l = strlen(comma) + 1 + last_name_len; new_name = (char *) fs_get((l+1) * sizeof(char)); strncpy(new_name, comma, l); new_name[l] = '\0'; strncat(new_name, " ", l+1-1-strlen(new_name)); new_name[l] = '\0'; strncat(new_name, fullname, MIN(last_name_len,l+1-1-strlen(new_name))); new_name[l] = '\0'; } else new_name = cpystr(fullname); return(new_name); } /* * Clear reference flags in preparation for a recursive lookup. * * For loop detection during address book look up. This clears all the * referenced flags, then as the lookup proceeds the referenced flags can * be checked and set. */ void adrbk_clearrefs(AdrBk *ab) { adrbk_cntr_t entry_num; AdrBk_Entry *ae; dprint((9, "- adrbk_clearrefs -\n")); if(!ab) return; for(entry_num = 0; entry_num < ab->count; entry_num++){ ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num); ae->referenced = 0; } } /* * Allocate a new AdrBk_Entry */ AdrBk_Entry * adrbk_newentry(void) { AdrBk_Entry *ae; ae = (AdrBk_Entry *) fs_get(sizeof(AdrBk_Entry)); defvalue_ae(ae); return(ae); } /* * Just sets ae values to default. * Parts should be freed before calling this or they will leak. */ void defvalue_ae(AdrBk_Entry *ae) { ae->nickname = empty; ae->fullname = empty; ae->addr.addr = empty; ae->fcc = empty; ae->extra = empty; ae->tag = NotSet; ae->referenced = 0; } AdrBk_Entry * copy_ae(AdrBk_Entry *src) { AdrBk_Entry *a; a = adrbk_newentry(); a->tag = src->tag; a->nickname = cpystr(src->nickname ? src->nickname : ""); a->fullname = cpystr(src->fullname ? src->fullname : ""); a->fcc = cpystr(src->fcc ? src->fcc : ""); a->extra = cpystr(src->extra ? src->extra : ""); if(a->tag == Single) a->addr.addr = cpystr(src->addr.addr ? src->addr.addr : ""); else if(a->tag == List){ char **p; int i, n; /* count list */ for(p = src->addr.list; p && *p; p++) ;/* do nothing */ if(p == NULL) n = 0; else n = p - src->addr.list; a->addr.list = (char **)fs_get((n+1) * sizeof(char *)); for(i = 0; i < n; i++) a->addr.list[i] = cpystr(src->addr.list[i]); a->addr.list[n] = NULL; } return(a); } /* * Add an entry to the address book, or modify an existing entry * * Args: ab -- address book to add to * old_entry_num -- the entry we want to modify. If this is NO_NEXT, then * we look up the nickname passed in to see if that's the * entry to modify, else it is a new entry. * nickname -- the nickname for new entry * fullname -- the fullname for new entry * address -- the address for new entry * fcc -- the fcc for new entry * extra -- the extra field for new entry * tag -- the type of new entry * new_entry_num -- return entry_num of new or modified entry here * resort_happened -- means that more than just the current entry changed, * either something was added or order was changed * enable_intr -- tell adrbk_write to enable interrupt handling * be_quiet -- tell adrbk_write to not do percent done messages * write_it -- only do adrbk_write if this is set * * Result: return code: 0 all went well * -2 error writing address book, check errno * -3 no modification, the tag given didn't match * existing tag * -4 tabs are in one of the fields passed in * * If the nickname exists in the address book already, the operation is * considered a modification even if the case does not match exactly, * otherwise it is an add. The entry the operation occurs on is returned * in new. All fields are set to those passed in; that is, passing in NULL * even on a modification will set those fields to NULL as opposed to leaving * them unchanged. It is acceptable to pass in the current strings * in the entry in the case of modification. For address lists, the * structure passed in is what is used, so the storage has to all have * come from fs_get(). If the pointer passed in is the same as * the current field, no change is made. */ int adrbk_add(AdrBk *ab, a_c_arg_t old_entry_num, char *nickname, char *fullname, char *address, char *fcc, char *extra, Tag tag, adrbk_cntr_t *new_entry_num, int *resort_happened, int enable_intr, int be_quiet, int write_it) { AdrBk_Entry *a; AdrBk_Entry *ae; adrbk_cntr_t old_enum; adrbk_cntr_t new_enum; int (*cmp_func)(const qsort_t *, const qsort_t *); int retval = 0; int need_write = 0; int set_mangled = 0; dprint((3, "- adrbk_add(%s) -\n", nickname ? nickname : "")); if(!ab) return -2; /* ---- Make sure there are no tabs in the stuff to add ------*/ if((nickname != NULL && strindex(nickname, TAB) != NULL) || (fullname != NULL && strindex(fullname, TAB) != NULL) || (fcc != NULL && strindex(fcc, TAB) != NULL) || (tag == Single && address != NULL && strindex(address, TAB) != NULL)) return -4; /* * Are we adding or updating ? * * If old_entry_num was passed in, we're updating that. If nickname * already exists, we're updating that entry. Otherwise, this is an add. */ if((adrbk_cntr_t)old_entry_num != NO_NEXT){ ae = adrbk_get_ae(ab, old_entry_num); if(ae) old_enum = (adrbk_cntr_t)old_entry_num; } else ae = adrbk_lookup_by_nick(ab, nickname, &old_enum); if(ae == NULL){ /*----- adding a new entry ----*/ ae = adrbk_newentry(); ae->tag = tag; if(nickname) ae->nickname = cpystr(nickname); if(fullname) ae->fullname = cpystr(fullname); if(fcc) ae->fcc = cpystr(fcc); if(extra) ae->extra = cpystr(extra); if(tag == Single) ae->addr.addr = cpystr(address); else ae->addr.list = (char **)NULL; cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ? cmp_ae_by_full_lists_last : (ab->sort_rule == AB_SORT_RULE_FULL) ? cmp_ae_by_full : (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ? cmp_ae_by_nick_lists_last : /* (ab->sort_rule == AB_SORT_RULE_NICK) */ cmp_ae_by_nick; if(ab->sort_rule == AB_SORT_RULE_NONE) /* put it last */ new_enum = ab->count; else /* Find slot for it */ for(new_enum = 0, a = adrbk_get_ae(ab, (a_c_arg_t) new_enum); a != (AdrBk_Entry *)NULL; a = adrbk_get_ae(ab, (a_c_arg_t) (++new_enum))){ if((*cmp_func)((qsort_t *)&a, (qsort_t *)&ae) >= 0) break; } /* Insert ae before entry new_enum. */ insert_ab_entry(ab, (a_c_arg_t) new_enum, ae, 0); if(F_OFF(F_EXPANDED_DISTLISTS,ps_global)) exp_add_nth(ab->exp, (a_c_arg_t)new_enum); exp_add_nth(ab->selects, (a_c_arg_t)new_enum); /* * insert_ab_entry copies the pointers of ae so the things * being pointed to (nickname, ...) are still in use. * Don't free them but free the ae struct itself. */ fs_give((void **) &ae); /*---- return in pointer if requested -----*/ if(new_entry_num) *new_entry_num = new_enum; if(resort_happened) *resort_happened = 1; if(write_it) retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num, &set_mangled, enable_intr, be_quiet); } else{ /*----- Updating an existing entry ----*/ int fix_tries = 0; if(ae->tag != tag) return -3; /* * Instead of just freeing and reallocating here we attempt to re-use * the space that was already allocated if possible. */ if(ae->nickname != nickname && ae->nickname != NULL && nickname != NULL && strcmp(nickname, ae->nickname) != 0){ need_write++; fix_tries++; /* can use already alloc'd space */ if(ae->nickname != NULL && nickname != NULL && strlen(nickname) <= strlen(ae->nickname)){ strncpy(ae->nickname, nickname, strlen(ae->nickname)+1); } else{ if(ae->nickname != NULL && ae->nickname != empty) fs_give((void **)&ae->nickname); ae->nickname = nickname ? cpystr(nickname) : nickname; } } if(ae->fullname != fullname && ae->fullname != NULL && fullname != NULL && strcmp(fullname, ae->fullname) != 0){ need_write++; if(ae->fullname != NULL && fullname != NULL && strlen(fullname) <= strlen(ae->fullname)){ strncpy(ae->fullname, fullname, strlen(ae->fullname)+1); } else{ if(ae->fullname != NULL && ae->fullname != empty) fs_give((void **)&ae->fullname); ae->fullname = fullname ? cpystr(fullname) : fullname; } } if(ae->fcc != fcc && ae->fcc != NULL && fcc != NULL && strcmp(fcc, ae->fcc) != 0){ need_write++; if(ae->fcc != NULL && fcc != NULL && strlen(fcc) <= strlen(ae->fcc)){ strncpy(ae->fcc, fcc, strlen(ae->fcc)+1); } else{ if(ae->fcc != NULL && ae->fcc != empty) fs_give((void **)&ae->fcc); ae->fcc = fcc ? cpystr(fcc) : fcc; } } if(ae->extra != extra && ae->extra != NULL && extra != NULL && strcmp(extra, ae->extra) != 0){ need_write++; if(ae->extra != NULL && extra != NULL && strlen(extra) <= strlen(ae->extra)){ strncpy(ae->extra, extra, strlen(ae->extra)+1); } else{ if(ae->extra != NULL && ae->extra != empty) fs_give((void **)&ae->extra); ae->extra = extra ? cpystr(extra) : extra; } } if(tag == Single){ /*---- Single ----*/ if(ae->addr.addr != address && ae->addr.addr != NULL && address != NULL && strcmp(address, ae->addr.addr) != 0){ need_write++; fix_tries++; if(ae->addr.addr != NULL && address != NULL && strlen(address) <= strlen(ae->addr.addr)){ strncpy(ae->addr.addr, address, strlen(ae->addr.addr)+1); } else{ if(ae->addr.addr != NULL && ae->addr.addr != empty) fs_give((void **)&ae->addr.addr); ae->addr.addr = address ? cpystr(address) : address; } } } else{ /*---- List -----*/ /* * We don't mess with lists here. * The caller has to do it with adrbk_listadd(). */ ;/* do nothing */ } /*---------- Make sure it's still in order ---------*/ /* * old_enum is where ae is currently located * put it where it belongs */ if(need_write){ new_enum = re_sort_particular_entry(ab, (a_c_arg_t) old_enum); if(old_enum != new_enum) fix_tries++; } else new_enum = old_enum; /*---- return in pointer if requested -----*/ if(new_entry_num) *new_entry_num = new_enum; if(resort_happened) *resort_happened = (old_enum != new_enum); if(fix_tries) repair_abook_tries(ab); if(write_it && need_write){ int sort_happened = 0; retval = adrbk_write(ab, (a_c_arg_t) new_enum, new_entry_num, &sort_happened, enable_intr, be_quiet); set_mangled = sort_happened; if(new_entry_num) new_enum = (*new_entry_num); if(resort_happened && (sort_happened || (old_enum != new_enum))) *resort_happened = 1; } else retval = 0; } if(set_mangled) ps_global->mangled_screen = 1; return(retval); } /* * Similar to adrbk_add, but lower cost. No sorting is done, the new entry * goes on the end. This won't work if it is an edit instead of an append. * The address book is not committed to disk. * * Args: ab -- address book to add to * nickname -- the nickname for new entry * fullname -- the fullname for new entry * address -- the address for new entry * fcc -- the fcc for new entry * extra -- the extra field for new entry * tag -- the type of new entry * * Result: return code: 0 all went well * -2 error writing address book, check errno * -3 no modification, the tag given didn't match * existing tag * -4 tabs are in one of the fields passed in */ int adrbk_append(AdrBk *ab, char *nickname, char *fullname, char *address, char *fcc, char *extra, Tag tag, adrbk_cntr_t *new_entry_num) { AdrBk_Entry *ae; dprint((3, "- adrbk_append(%s) -\n", nickname ? nickname : "")); if(!ab) return -2; /* ---- Make sure there are no tabs in the stuff to add ------*/ if((nickname != NULL && strindex(nickname, TAB) != NULL) || (fullname != NULL && strindex(fullname, TAB) != NULL) || (fcc != NULL && strindex(fcc, TAB) != NULL) || (tag == Single && address != NULL && strindex(address, TAB) != NULL)) return -4; ae = adrbk_newentry(); ae->tag = tag; if(nickname) ae->nickname = cpystr(nickname); if(fullname) ae->fullname = cpystr(fullname); if(fcc) ae->fcc = cpystr(fcc); if(extra) ae->extra = cpystr(extra); if(tag == Single) ae->addr.addr = cpystr(address); else ae->addr.list = (char **)NULL; if(new_entry_num) *new_entry_num = ab->count; insert_ab_entry(ab, (a_c_arg_t) ab->count, ae, 0); /* * insert_ab_entry copies the pointers of ae so the things * being pointed to (nickname, ...) are still in use. * Don't free them but free the ae struct itself. */ fs_give((void **) &ae); return(0); } /* * The entire address book is assumed sorted correctly except perhaps for * entry number cur. Put it in the correct place. Return the new entry * number for cur. */ adrbk_cntr_t re_sort_particular_entry(AdrBk *ab, a_c_arg_t cur) { AdrBk_Entry *ae_cur, *ae_prev, *ae_next, *ae_small_enough, *ae_big_enough; long small_enough; adrbk_cntr_t big_enough; adrbk_cntr_t new_entry_num; int (*cmp_func)(const qsort_t *, const qsort_t *); dprint((9, "- re_sort -\n")); cmp_func = (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ? cmp_ae_by_full_lists_last : (ab->sort_rule == AB_SORT_RULE_FULL) ? cmp_ae_by_full : (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ? cmp_ae_by_nick_lists_last : /* (ab->sort_rule == AB_SORT_RULE_NICK) */ cmp_ae_by_nick; new_entry_num = (adrbk_cntr_t) cur; if(ab->sort_rule == AB_SORT_RULE_NONE) return(new_entry_num); ae_cur = adrbk_get_ae(ab, cur); if(cur > 0) ae_prev = adrbk_get_ae(ab, cur - 1); if(cur < ab->count -1) ae_next = adrbk_get_ae(ab, cur + 1); /* * A possible optimization here would be to implement some sort of * binary search to find where it goes instead of stepping through the * entries one at a time. */ if(cur > 0 && (*cmp_func)((qsort_t *)&ae_cur,(qsort_t *)&ae_prev) < 0){ /*--- Out of order, needs to be moved up ----*/ for(small_enough = (long)cur - 2; small_enough >= 0L; small_enough--){ ae_small_enough = adrbk_get_ae(ab,(a_c_arg_t) small_enough); if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_small_enough) >= 0) break; } new_entry_num = (adrbk_cntr_t)(small_enough + 1L); move_ab_entry(ab, cur, (a_c_arg_t) new_entry_num); } else if(cur < ab->count - 1 && (*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_next) > 0){ /*---- Out of order needs, to be moved towards end of list ----*/ for(big_enough = (adrbk_cntr_t)(cur + 2); big_enough < ab->count; big_enough++){ ae_big_enough = adrbk_get_ae(ab, (a_c_arg_t) big_enough); if((*cmp_func)((qsort_t *)&ae_cur, (qsort_t *)&ae_big_enough) <= 0) break; } new_entry_num = big_enough - 1; move_ab_entry(ab, cur, (a_c_arg_t) big_enough); } dprint((9, "- re_sort done -\n")); return(new_entry_num); } /* * Delete an entry from the address book * * Args: ab -- the address book * entry_num -- entry to delete * save_deleted -- save deleted as a #DELETED- entry * enable_intr -- tell adrbk_write to enable interrupt handling * be_quiet -- tell adrbk_write to not do percent done messages * write_it -- only do adrbk_write if this is set * * Result: returns: 0 if all went well * -1 if there is no such entry * -2 error writing address book, check errno */ int adrbk_delete(AdrBk *ab, a_c_arg_t entry_num, int save_deleted, int enable_intr, int be_quiet, int write_it) { int retval = 0; int set_mangled = 0; dprint((3, "- adrbk_delete(%ld) -\n", (long)entry_num)); if(!ab) return -2; delete_ab_entry(ab, entry_num, save_deleted); if(F_OFF(F_EXPANDED_DISTLISTS,ps_global)) exp_del_nth(ab->exp, entry_num); exp_del_nth(ab->selects, entry_num); if(write_it) retval = adrbk_write(ab, 0, NULL, &set_mangled, enable_intr, be_quiet); if(set_mangled) ps_global->mangled_screen = 1; return(retval); } /* * Delete an address out of an address list * * Args: ab -- the address book * entry_num -- the address list we are deleting from * addr -- address in above list to be deleted * * Result: 0: Deletion complete, address book written * -1: Address for deletion not found * -2: Error writing address book. Check errno. * * The address to be deleted is located by matching the string. */ int adrbk_listdel(AdrBk *ab, a_c_arg_t entry_num, char *addr) { char **p, *to_free; AdrBk_Entry *ae; int ret; int set_mangled = 0; dprint((3, "- adrbk_listdel(%ld) -\n", (long) entry_num)); if(!ab || entry_num >= ab->count) return -2; if(!addr) return -1; ae = adrbk_get_ae(ab, entry_num); if(ae->tag != List) return -1; for(p = ae->addr.list; *p; p++) if(strcmp(*p, addr) == 0) break; if(*p == NULL) return -1; /* note storage to be freed */ if(*p != empty) to_free = *p; else to_free = NULL; /* slide all the entries below up (including NULL) */ for(; *p; p++) *p = *(p+1); if(to_free) fs_give((void **) &to_free); ret = adrbk_write(ab, 0, NULL, &set_mangled, 1, 0); if(set_mangled) ps_global->mangled_screen = 1; return(ret); } /* * Delete all addresses out of an address list * * Args: ab -- the address book * entry_num -- the address list we are deleting from * * Result: 0: Deletion complete, address book written * -1: Address for deletion not found * -2: Error writing address book. Check errno. */ int adrbk_listdel_all(AdrBk *ab, a_c_arg_t entry_num) { char **p; AdrBk_Entry *ae; dprint((3, "- adrbk_listdel_all(%ld) -\n", (long) entry_num)); if(!ab || entry_num >= ab->count) return -2; ae = adrbk_get_ae(ab, entry_num); if(ae->tag != List) return -1; /* free old list */ for(p = ae->addr.list; p && *p; p++) if(*p != empty) fs_give((void **)p); if(ae->addr.list) fs_give((void **) &ae->addr.list); ae->addr.list = NULL; return 0; } /* * Add a list of addresses to an already existing address list * * Args: ab -- the address book * entry_num -- the address list we are adding to * addrs -- address list to be added * enable_intr -- tell adrbk_write to enable interrupt handling * be_quiet -- tell adrbk_write to not do percent done messages * write_it -- only do adrbk_write if this is set * * Result: returns 0 : addition made, address book written * -1 : addition to non-list attempted * -2 : error writing address book -- check errno */ int adrbk_nlistadd(AdrBk *ab, a_c_arg_t entry_num, adrbk_cntr_t *new_entry_num, int *resort_happened, char **addrs, int enable_intr, int be_quiet, int write_it) { char **p; int cur_size, size_of_additional_list, new_size; int i, rc = 0; int set_mangled = 0; AdrBk_Entry *ae; dprint((3, "- adrbk_nlistadd(%ld) -\n", (long) entry_num)); if(!ab || entry_num >= ab->count) return -2; ae = adrbk_get_ae(ab, entry_num); if(ae->tag != List) return -1; /* count up size of existing list */ for(p = ae->addr.list; p != NULL && *p != NULL; p++) ;/* do nothing */ cur_size = p - ae->addr.list; /* count up size of new list */ for(p = addrs; p != NULL && *p != NULL; p++) ;/* do nothing */ size_of_additional_list = p - addrs; new_size = cur_size + size_of_additional_list; /* make room at end of list for it */ if(cur_size == 0) ae->addr.list = (char **) fs_get(sizeof(char *) * (new_size + 1)); else fs_resize((void **) &ae->addr.list, sizeof(char *) * (new_size + 1)); /* Put new list at the end */ for(i = cur_size; i < new_size; i++) (ae->addr.list)[i] = cpystr(addrs[i - cur_size]); (ae->addr.list)[new_size] = NULL; /*---- sort it into the correct place ------*/ if(ab->sort_rule != AB_SORT_RULE_NONE) sort_addr_list(ae->addr.list); if(write_it) rc = adrbk_write(ab, entry_num, new_entry_num, &set_mangled, enable_intr, be_quiet); if(set_mangled){ ps_global->mangled_screen = 1; if(resort_happened) *resort_happened = 1; } return(rc); } /* * Set the valid variable if we determine that the address book has * been changed by something other than us. This means we should update * our view of the address book when next possible. * * Args ab -- AdrBk handle * do_it_now -- If > 0, check now regardless * If = 0, check if time since last chk more than default * If < 0, check if time since last chk more than -do_it_now */ void adrbk_check_validity(AdrBk *ab, long int do_it_now) { dprint((9, "- adrbk_check_validity(%s) -\n", (ab && ab->filename) ? ab->filename : "")); if(!ab || ab->flags & FILE_OUTOFDATE) return; adrbk_check_local_validity(ab, do_it_now); if(ab->type == Imap && !(ab->flags & FILE_OUTOFDATE || ab->rd->flags & REM_OUTOFDATE)) rd_check_remvalid(ab->rd, do_it_now); } /* * Set the valid variable if we determine that the address book has * been changed by something other than us. This means we should update * our view of the address book when next possible. * * Args ab -- AdrBk handle * do_it_now -- If > 0, check now regardless * If = 0, check if time since last chk more than default * If < 0, check if time since last chk more than -do_it_now */ void adrbk_check_local_validity(AdrBk *ab, long int do_it_now) { time_t mtime, chk_interval; dprint((9, "- adrbk_check_local_validity(%s) -\n", (ab && ab->filename) ? ab->filename : "")); if(!ab) return; if(do_it_now < 0L){ chk_interval = -1L * do_it_now; do_it_now = 0L; } else chk_interval = FILE_VALID_CHK_INTERVAL; if(!do_it_now && get_adj_time() <= ab->last_local_valid_chk + chk_interval) return; ab->last_local_valid_chk = get_adj_time(); /* * Check local file for a modification time change. * If this is out of date, don't even bother checking for the remote * folder being out of date. That will get fixed when we reopen. */ if(!(ab->flags & FILE_OUTOFDATE) && ab->last_change_we_know_about != (time_t)(-1) && (mtime=get_adj_name_file_mtime(ab->filename)) != (time_t)(-1) && ab->last_change_we_know_about != mtime){ dprint((2, "adrbk_check_local_validity: addrbook %s has changed\n", ab->filename ? ab->filename : "?")); ab->flags |= FILE_OUTOFDATE; } } /* * See if we can re-use an existing stream. * * [ We don't believe we need this anymore now that the stuff in pine.c ] * [ is recycling streams for us, but we haven't thought it through all ] * [ the way enough to get rid of this. Hubert 2003-07-09 ] * * Args name -- Name of folder we want a stream for. * * Returns -- A mail stream suitable for status cmd or append cmd. * NULL if none were available. */ MAILSTREAM * adrbk_handy_stream(char *name) { MAILSTREAM *stat_stream = NULL; int i; dprint((9, "- adrbk_handy_stream(%s) -\n", name ? name : "?")); stat_stream = sp_stream_get(name, SP_SAME); /* * Look through our imap streams to see if there is one we can use. */ for(i = 0; !stat_stream && i < as.n_addrbk; i++){ PerAddrBook *pab; pab = &as.adrbks[i]; if(pab->address_book && pab->address_book->type == Imap && pab->address_book->rd && pab->address_book->rd->type == RemImap && same_stream(name, pab->address_book->rd->t.i.stream)){ stat_stream = pab->address_book->rd->t.i.stream; pab->address_book->rd->last_use = get_adj_time(); dprint((7, "%s: used other abook stream for status (%ld)\n", pab->address_book->orig_filename ? pab->address_book->orig_filename : "?", (long)pab->address_book->rd->last_use)); } } dprint((9, "adrbk_handy_stream: returning %s\n", stat_stream ? "good stream" : "NULL")); return(stat_stream); } /* * Close address book * * All that is done here is to free the storage, since the address book is * rewritten on every change. */ void adrbk_close(AdrBk *ab) { int we_cancel = 0; dprint((4, "- adrbk_close(%s) -\n", (ab && ab->filename) ? ab->filename : "")); if(!ab) return; if(ab->rd) rd_close_remdata(&ab->rd); if(ab->fp) (void)fclose(ab->fp); if(ab->our_filecopy && ab->filename != ab->our_filecopy){ our_unlink(ab->our_filecopy); fs_give((void**) &ab->our_filecopy); } if(ab->exp){ exp_free(ab->exp); fs_give((void **)&ab->exp); /* free head of list, too */ } if(ab->checks){ exp_free(ab->checks); fs_give((void **)&ab->checks); /* free head of list, too */ } if(ab->selects){ exp_free(ab->selects); fs_give((void **)&ab->selects); /* free head of list, too */ } if(ab->arr){ adrbk_cntr_t entry_num; /* * ab->arr is an allocated array. Each element of the array contains * several pointers that point to other allocated stuff, so we * free_ae_parts to get those and then free the array after the loop. */ for(entry_num = 0; entry_num < ab->count; entry_num++) free_ae_parts(&ab->arr[entry_num]); fs_give((void **) &ab->arr); } if(ab->del){ adrbk_cntr_t entry_num; for(entry_num = 0; entry_num < ab->del_count; entry_num++) free_ae_parts(&ab->del[entry_num]); fs_give((void **) &ab->del); } if(ab->nick_trie) free_abook_trie(&ab->nick_trie); if(ab->addr_trie) free_abook_trie(&ab->addr_trie); if(ab->full_trie) free_abook_trie(&ab->full_trie); if(ab->revfull_trie) free_abook_trie(&ab->revfull_trie); if(we_cancel) cancel_busy_cue(0); if(ab->filename){ if(ab->flags & DEL_FILE) our_unlink(ab->filename); fs_give((void**)&ab->filename); } if(ab->orig_filename) fs_give((void**)&ab->orig_filename); fs_give((void **) &ab); } void adrbk_partial_close(AdrBk *ab) { dprint((4, "- adrbk_partial_close(%s) -\n", (ab && ab->filename) ? ab->filename : "")); exp_free(ab->exp); /* leaves head of list */ exp_free(ab->checks); /* leaves head of list */ } /* * It has been noticed that this stream is dead and it is about to * be removed from the stream pool. We may have a pointer to it for one * of the remote address books. We have to note that the pointer is stale. */ void note_closed_adrbk_stream(MAILSTREAM *stream) { PerAddrBook *pab; int i; if(!stream) return; for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->address_book && pab->address_book->type == Imap && pab->address_book->rd && pab->address_book->rd->type == RemImap && (stream == pab->address_book->rd->t.i.stream)){ dprint((4, "- note_closed_adrbk_stream(%s) -\n", (pab->address_book && pab->address_book->orig_filename) ? pab->address_book->orig_filename : "")); pab->address_book->rd->t.i.stream = NULL; } } } static adrbk_cntr_t tot_for_percent; static adrbk_cntr_t entry_num_for_percent; /* * Write out the address book. * * If be_quiet is set, don't turn on busy_cue. * * If enable_intr_handling is set, turn on and off interrupt handling. * * Format is as in comment in the adrbk_open routine. Lines are wrapped * to be under 80 characters. This is called on every change to the * address book. Write is first to a temporary file, * which is then renamed to be the real address book so that we won't * destroy the real address book in case of something like a full file * system. * * Writing a temp file and then renaming has the bad side affect of * destroying links. It also overrides any read only permissions on * the mail file since rename ignores such permissions. However, we * handle readonly-ness in addrbook.c before we call this. * We retain the permissions by doing a stat on the old file and a * chmod on the new one (with file_attrib_copy). * * In pre-alpine pine the address book entries were encoded on disk. * We would be happy with raw UTF-8 now but in order to preserve some * backwards compatibility we encode the entries before writing. * We first try to translate to the user's character set and encode * in that, else we use UTF-8 but with 1522 encoding. * * Returns: 0 write was successful * -2 write failed * -5 interrupted */ int adrbk_write(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num, int *sort_happened, int enable_intr_handling, int be_quiet) { FILE *ab_stream = NULL; AdrBk_Entry *ae = NULL; adrbk_cntr_t entry_num; #ifndef DOS void (*save_sighup)(int); #endif int max_nick = 0, max_full = 0, full_two = 0, full_three = 0, max_fcc = 0, fcc_two = 0, fcc_three = 0, max_addr = 0, addr_two = 0, addr_three = 0, this_nick_width, this_full_width, this_addr_width, this_fcc_width, fd, i; int interrupt_happened = 0, we_cancel = 0, we_turned_on = 0; char *temp_filename = NULL; WIDTH_INFO_S *widths; if(!ab) return -2; dprint((2, "- adrbk_write(\"%s\") - writing %lu entries\n", ab->filename ? ab->filename : "", (unsigned long) ab->count)); errno = 0; adrbk_check_local_validity(ab, 1L); /* verify that file has not been changed by something else */ if(ab->flags & FILE_OUTOFDATE){ /* It has changed! */ q_status_message(SM_ORDER | SM_DING, 5, 15, /* TRANSLATORS: The address book was changed by something else so alpine is not making the change the user wanted to make to avoid damaging the address book. */ _("Addrbook changed by another process, aborting our change to avoid damage...")); dprint((1, "adrbk_write: addrbook %s changed while we had it open, aborting write\n", ab->filename ? ab->filename : "?")); longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } /* * Verify that remote folder has not been * changed by something else (not if checked in last 5 seconds). * * The -5 is so we won't check every time on a series of quick writes. */ rd_check_remvalid(ab->rd, -5L); if(ab->type == Imap){ int ro; /* * We'll eventually need this open to write to remote, so see if * we can open it now. */ rd_open_remote(ab->rd); /* * Did someone else change the remote copy? */ if((ro=rd_remote_is_readonly(ab->rd)) || ab->rd->flags & REM_OUTOFDATE){ if(ro == 1){ q_status_message(SM_ORDER | SM_DING, 5, 15, _("Can't access remote addrbook, aborting change...")); dprint((1, "adrbk_write: Can't write to remote addrbook %s, aborting write: open failed\n", ab->rd->rn ? ab->rd->rn : "?")); } else if(ro == 2){ if(!(ab->rd->flags & NO_META_UPDATE)){ unsigned long save_chk_nmsgs; /* * Should have some non-type-specific method of doing this. */ switch(ab->rd->type){ case RemImap: save_chk_nmsgs = ab->rd->t.i.chk_nmsgs; ab->rd->t.i.chk_nmsgs = 0;/* cause it to be OUTOFDATE */ rd_write_metadata(ab->rd, 0); ab->rd->t.i.chk_nmsgs = save_chk_nmsgs; break; default: q_status_message(SM_ORDER | SM_DING, 3, 5, "Adrbk_write: Type not supported"); break; } } q_status_message(SM_ORDER | SM_DING, 5, 15, _("No write permission for remote addrbook, aborting change...")); } else{ q_status_message(SM_ORDER | SM_DING, 5, 15, _("Remote addrbook changed, aborting our change to avoid damage...")); dprint((1, "adrbk_write: remote addrbook %s changed while we had it open, aborting write\n", ab->orig_filename ? ab->orig_filename : "?")); } rd_close_remote(ab->rd); longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } } #ifndef DOS save_sighup = (void (*)(int))signal(SIGHUP, SIG_IGN); #endif /* * If we want to be able to modify the address book, we will * need a temp_filename in the same directory as our_filecopy. */ if(!(ab->our_filecopy && ab->our_filecopy[0]) || !(temp_filename = tempfile_in_same_dir(ab->our_filecopy,"a1",NULL)) || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){ dprint((1, "adrbk_write(%s): failed opening temp file (%s)\n", ab->filename ? ab->filename : "?", temp_filename ? temp_filename : "NULL")); goto io_error; } ab_stream = fdopen(fd, "wb"); if(ab_stream == NULL){ dprint((1, "adrbk_write(%s): fdopen failed\n", temp_filename ? temp_filename : "?")); goto io_error; } if(adrbk_is_in_sort_order(ab, be_quiet)){ if(sort_happened) *sort_happened = 0; if(new_entry_num) *new_entry_num = current_entry_num; } else{ if(sort_happened) *sort_happened = 1; (void) adrbk_sort(ab, current_entry_num, new_entry_num, be_quiet); } /* accept keyboard interrupts */ if(enable_intr_handling) we_turned_on = intr_handling_on(); if(!be_quiet){ tot_for_percent = MAX(ab->count, 1); entry_num_for_percent = 0; we_cancel = busy_cue(_("Saving address book"), percent_abook_saved, 0); } writing = 1; /* * If there are any old deleted entries, copy them to new file. */ if(ab->del_count > 0){ char *nickname; long delindex; /* * If there are deleted entries old enough that we no longer want * to save them, remove them from the list. */ for(delindex = (long) ab->del_count-1; delindex >= 0L && ab->del_count > 0; delindex--){ ae = adrbk_get_delae(ab, (a_c_arg_t) delindex); nickname = ae->nickname; if(strncmp(nickname, DELETED, DELETED_LEN) == 0 && isdigit((unsigned char)nickname[DELETED_LEN]) && isdigit((unsigned char)nickname[DELETED_LEN+1]) && nickname[DELETED_LEN+2] == '/' && isdigit((unsigned char)nickname[DELETED_LEN+3]) && isdigit((unsigned char)nickname[DELETED_LEN+4]) && nickname[DELETED_LEN+5] == '/' && isdigit((unsigned char)nickname[DELETED_LEN+6]) && isdigit((unsigned char)nickname[DELETED_LEN+7]) && nickname[DELETED_LEN+8] == '#'){ int year, month, day; struct tm *tm_before; time_t now, before; now = time((time_t *)0); before = now - (time_t)ABOOK_DELETED_EXPIRE_TIME; tm_before = localtime(&before); tm_before->tm_mon++; /* * Check to see if it is older than 100 days. */ year = atoi(&nickname[DELETED_LEN]); month = atoi(&nickname[DELETED_LEN+3]); day = atoi(&nickname[DELETED_LEN+6]); if(year < 95) year += 100; /* this breaks in year 2095 */ /* * remove it if it is more than 100 days old */ if(!(year > tm_before->tm_year || (year == tm_before->tm_year && month > tm_before->tm_mon) || (year == tm_before->tm_year && month == tm_before->tm_mon && day >= tm_before->tm_mday))){ /* it's old, dump it */ free_ae_parts(ae); ab->del_count--; /* patch the array by moving everything below up */ if(delindex < ab->del_count){ memmove(&ab->del[delindex], &ab->del[delindex+1], (ab->del_count - delindex) * sizeof(AdrBk_Entry)); } } } } /* write out what remains */ if(ab->del_count){ for(delindex = 0L; delindex < ab->del_count; delindex++){ ae = adrbk_get_delae(ab, (a_c_arg_t) delindex); if(write_single_abook_entry(ae, ab_stream, NULL, NULL, NULL, NULL) == EOF){ dprint((1, "adrbk_write(%s): failed writing deleted entry\n", temp_filename ? temp_filename : "?")); goto io_error; } } } else{ /* nothing left, get rid of del array */ fs_give((void **) &ab->del); } } if(ab->del_count) dprint((4, " adrbk_write: saving %ld deleted entries\n", (long) ab->del_count)); for(entry_num = 0; entry_num < ab->count; entry_num++){ entry_num_for_percent++; ALARM_BLIP(); ae = adrbk_get_ae(ab, (a_c_arg_t) entry_num); if(ae == (AdrBk_Entry *) NULL){ dprint((1, "adrbk_write(%s): can't find ae while writing addrbook, entry_num = %ld\n", ab->filename ? ab->filename : "?", (long) entry_num)); goto io_error; } /* write to temp file */ if(write_single_abook_entry(ae, ab_stream, &this_nick_width, &this_full_width, &this_addr_width, &this_fcc_width) == EOF){ dprint((1, "adrbk_write(%s): failed writing for entry %ld\n", temp_filename ? temp_filename : "?", (long) entry_num)); goto io_error; } /* keep track of widths */ max_nick = MAX(max_nick, this_nick_width); if(this_full_width > max_full){ full_three = full_two; full_two = max_full; max_full = this_full_width; } else if(this_full_width > full_two){ full_three = full_two; full_two = this_full_width; } else if(this_full_width > full_three){ full_three = this_full_width; } if(this_addr_width > max_addr){ addr_three = addr_two; addr_two = max_addr; max_addr = this_addr_width; } else if(this_addr_width > addr_two){ addr_three = addr_two; addr_two = this_addr_width; } else if(this_addr_width > addr_three){ addr_three = this_addr_width; } if(this_fcc_width > max_fcc){ fcc_three = fcc_two; fcc_two = max_fcc; max_fcc = this_fcc_width; } else if(this_fcc_width > fcc_two){ fcc_three = fcc_two; fcc_two = this_fcc_width; } else if(this_fcc_width > fcc_three){ fcc_three = this_fcc_width; } /* * Check to see if we've been interrupted. We check at the bottom * of the loop so that we can handle an interrupt in the last * iteration. */ if(enable_intr_handling && ps_global->intr_pending){ interrupt_happened++; goto io_error; } } if(we_cancel){ cancel_busy_cue(-1); we_cancel = 0; } if(enable_intr_handling && we_turned_on){ intr_handling_off(); we_turned_on = 0; } if(fclose(ab_stream) == EOF){ dprint((1, "adrbk_write: fclose for %s failed\n", temp_filename ? temp_filename : "?")); goto io_error; } ab_stream = (FILE *) NULL; file_attrib_copy(temp_filename, ab->our_filecopy); if(ab->fp){ (void) fclose(ab->fp); ab->fp = NULL; /* in case of problems */ } if((i=rename_file(temp_filename, ab->our_filecopy)) < 0){ dprint((1, "adrbk_write: rename(%s, %s) failed: %s\n", temp_filename ? temp_filename : "?", ab->our_filecopy ? ab->our_filecopy : "?", error_description(errno))); #ifdef _WINDOWS if(i == -5){ q_status_message2(SM_ORDER | SM_DING, 5, 7, _("Can't replace address book %.200sfile \"%.200s\""), (ab->type == Imap) ? "cache " : "", ab->filename); q_status_message(SM_ORDER | SM_DING, 5, 7, _("If another Alpine is running, quit that Alpine before updating address book.")); } #endif /* _WINDOWS */ goto io_error; } /* reopen fp to new file */ if(!(ab->fp = our_fopen(ab->our_filecopy, "rb"))){ dprint((1, "adrbk_write: can't reopen %s\n", ab->our_filecopy ? ab->our_filecopy : "?")); goto io_error; } if(temp_filename){ our_unlink(temp_filename); fs_give((void **) &temp_filename); } /* * Now copy our_filecopy back to filename. */ if(ab->filename != ab->our_filecopy){ int c, err = 0; char *lc; if(!(ab->filename && ab->filename[0]) || !(temp_filename = tempfile_in_same_dir(ab->filename,"a1",NULL)) || (fd = our_open(temp_filename, OPEN_WRITE_MODE, 0600)) < 0){ dprint((1, "adrbk_write(%s): failed opening temp file (%s)\n", ab->filename ? ab->filename : "?", temp_filename ? temp_filename : "NULL")); err++; } if(!err) ab_stream = fdopen(fd, "wb"); if(ab_stream == NULL){ dprint((1, "adrbk_write(%s): fdopen failed\n", temp_filename ? temp_filename : "?")); err++; } if(!err){ rewind(ab->fp); while((c = getc(ab->fp)) != EOF) if(putc(c, ab_stream) == EOF){ err++; break; } } if(!err && fclose(ab_stream) == EOF) err++; if(!err){ #ifdef _WINDOWS int tries = 3; #endif file_attrib_copy(temp_filename, ab->filename); /* * This could fail if somebody else has it open, but they should * only have it open for a short time. */ while(!err && rename_file(temp_filename, ab->filename) < 0){ #ifdef _WINDOWS if(i == -5){ if(--tries <= 0) err++; if(!err){ q_status_message2(SM_ORDER, 0, 3, _("Replace of \"%.200s\" failed, trying %.200s"), (lc=last_cmpnt(ab->filename)) ? lc : ab->filename, (tries > 1) ? "again" : "one more time"); display_message('x'); sleep(3); } } #else /* UNIX */ err++; #endif /* UNIX */ } } if(err){ q_status_message1(SM_ORDER | SM_DING, 5, 5, _("Copy of addrbook to \"%.200s\" failed, changes NOT saved!"), (lc=last_cmpnt(ab->filename)) ? lc : ab->filename); dprint((2, "adrbk_write: failed copying our_filecopy (%s)back to filename (%s): %s, continuing without a net\n", ab->our_filecopy ? ab->our_filecopy : "?", ab->filename ? ab->filename : "?", error_description(errno))); } } widths = &ab->widths; widths->max_nickname_width = MIN(max_nick, 99); widths->max_fullname_width = MIN(max_full, 99); widths->max_addrfield_width = MIN(max_addr, 99); widths->max_fccfield_width = MIN(max_fcc, 99); widths->third_biggest_fullname_width = MIN(full_three, 99); widths->third_biggest_addrfield_width = MIN(addr_three, 99); widths->third_biggest_fccfield_width = MIN(fcc_three, 99); /* record new change date of addrbook file */ ab->last_change_we_know_about = get_adj_name_file_mtime(ab->filename); #ifndef DOS (void)signal(SIGHUP, save_sighup); #endif /* * If this is a remote addressbook, copy the file over. * If it fails we warn but continue to operate on the changed, * locally cached addressbook file. */ if(ab->type == Imap){ int e; char datebuf[200]; datebuf[0] = '\0'; if(we_cancel) cancel_busy_cue(-1); we_cancel = busy_cue(_("Copying to remote addressbook"), NULL, 1); /* * We don't want a cookie upgrade to blast our data in rd->lf by * copying back the remote data before doing the upgrade, and then * proceeding to finish with that data. So we tell rd_upgrade_cookie * about that here. */ ab->rd->flags |= BELIEVE_CACHE; if((e = rd_update_remote(ab->rd, datebuf)) != 0){ if(e == -1){ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error opening temporary addrbook file %.200s: %.200s"), ab->rd->lf, error_description(errno)); dprint((1, "adrbk_write: error opening temp file %s\n", ab->rd->lf ? ab->rd->lf : "?")); } else{ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error copying to %.200s: %.200s"), ab->rd->rn, error_description(errno)); dprint((1, "adrbk_write: error copying from %s to %s\n", ab->rd->lf ? ab->rd->lf : "?", ab->rd->rn ? ab->rd->rn : "?")); } q_status_message(SM_ORDER | SM_DING, 5, 5, _("Copy of addrbook to remote folder failed, changes NOT saved remotely")); } else{ rd_update_metadata(ab->rd, datebuf); ab->rd->read_status = 'W'; dprint((7, "%s: copied local to remote in adrbk_write (%ld)\n", ab->rd->rn ? ab->rd->rn : "?", (long)ab->rd->last_use)); } ab->rd->flags &= ~BELIEVE_CACHE; } writing = 0; if(we_cancel) cancel_busy_cue(0); if(temp_filename){ our_unlink(temp_filename); fs_give((void **) &temp_filename); } return 0; io_error: if(we_cancel) cancel_busy_cue(-1); if(enable_intr_handling && we_turned_on) intr_handling_off(); #ifndef DOS (void)signal(SIGHUP, save_sighup); #endif if(interrupt_happened){ q_status_message(0, 1, 2, _("Interrupt! Reverting to previous version")); display_message('x'); dprint((1, "adrbk_write(%s): Interrupt\n", ab->filename ? ab->filename : "?")); } else dprint((1, "adrbk_write(%s) (%s): some sort of io_error\n", ab->filename ? ab->filename : "?", ab->our_filecopy ? ab->our_filecopy : "?")); writing = 0; if(ab_stream != NULL){ fclose(ab_stream); ab_stream = (FILE *)NULL; } if(temp_filename){ our_unlink(temp_filename); fs_give((void **) &temp_filename); } adrbk_partial_close(ab); if(interrupt_happened){ errno = EINTR; /* for nicer error message */ return -5; } else return -2; } /* * Writes one addrbook entry with wrapping. Fills in widths * for display purposes. Returns 0, or EOF on error. * * Continuation lines always start with spaces. Tabs are treated as * separators, never as whitespace. When we output tab separators we * always put them on the ends of lines, never on the start of a line * after a continuation. That is, there is always something printable * after continuation spaces. */ int write_single_abook_entry(AdrBk_Entry *ae, FILE *fp, int *ret_nick_width, int *ret_full_width, int *ret_addr_width, int *ret_fcc_width) { int len = 0; int nick_width = 0, full_width = 0, addr_width = 0, fcc_width = 0; int tmplen, this_len; char *write_this = NULL; if(fp == (FILE *) NULL){ dprint((1, "write_single_abook_entry: fp is NULL\n")); return(EOF); } if(ae->nickname){ nick_width = utf8_width(ae->nickname); write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->nickname); this_len = strlen(write_this ? write_this : ""); /* * We aren't too concerned with where it wraps. * Long lines are ok as long as they aren't super long. */ if(len > 100 || (len+this_len > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fputs ind1 failed\n")); return(EOF); } len = this_len + INDENT; } len += this_len; if(fputs(write_this, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs nick failed\n")); return(EOF); } } else nick_width = 0; putc(TAB, fp); len++; if(ae->fullname){ full_width = utf8_width(ae->fullname); write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fullname); this_len = strlen(write_this ? write_this : ""); if(len > 100 || (len+this_len > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fputs ind2 failed\n")); return(EOF); } len = this_len + INDENT; } len += this_len; if(fputs(write_this, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs full failed\n")); return(EOF); } } else full_width = 0; putc(TAB, fp); len++; /* special case, make sure empty list has () */ if(ae->tag == List && ae->addr.list == NULL){ addr_width = 0; putc('(', fp); putc(')', fp); len += 2; } else if(ae->addr.addr != NULL || ae->addr.list != NULL){ if(ae->tag == Single){ /*----- Single: just one address ----*/ if(ae->addr.addr){ addr_width = utf8_width(ae->addr.addr); write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->addr.addr); this_len = strlen(write_this ? write_this : ""); if(len > 100 || (len+this_len > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fputs ind3 failed\n")); return(EOF); } len = this_len + INDENT; } len += this_len; if(fputs(write_this, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs addr failed\n")); return(EOF); } } else addr_width = 0; } else if(ae->tag == List){ register char **a2; /*----- List: a distribution list ------*/ putc('(', fp); len++; addr_width = 0; for(a2 = ae->addr.list; *a2 != NULL; a2++){ if(*a2){ if(a2 != ae->addr.list){ putc(',', fp); len++; } addr_width = MAX(addr_width, utf8_width(*a2)); write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, *a2); this_len = strlen(write_this ? write_this : ""); if(len > 100 || (len+this_len > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fputs ind3 failed\n")); return(EOF); } len = this_len + INDENT; } len += this_len; if(fputs(write_this, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs addrl failed\n")); return(EOF); } } } putc(')', fp); len++; } } /* If either fcc or extra exists, output both, otherwise, neither */ if((ae->fcc && ae->fcc[0]) || (ae->extra && ae->extra[0])){ putc(TAB, fp); len++; if(ae->fcc && ae->fcc[0]){ fcc_width = utf8_width(ae->fcc); write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->fcc); this_len = strlen(write_this ? write_this : ""); if(len > 100 || (len+this_len > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fputs ind4 failed\n")); return(EOF); } len = this_len + INDENT; } len += this_len; if(fputs(write_this, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs fcc failed\n")); return(EOF); } } else fcc_width = 0; putc(TAB, fp); len++; if(ae->extra && ae->extra[0]){ int space; char *cur, *end; char *extra_copy; write_this = backcompat_encoding_for_abook(tmp_20k_buf, 10000, tmp_20k_buf+10000, SIZEOF_20KBUF-10000, ae->extra); /* * Copy ae->extra and replace newlines with spaces. * The reason we do this is because the continuation lines * produced by rfc1522_encode may interact with the * continuation lines produced below in a bad way. * In particular, what can happen is that the 1522 continuation * newline can be followed immediately by a newline produced * below. That breaks the continuation since that is a * line with nothing on it. Just turn 1522 continuations into * spaces, which work fine with 1522_decode. */ extra_copy = cpystr(write_this); REPLACE_NEWLINES_WITH_SPACE(extra_copy); tmplen = strlen(extra_copy); if(len > 100 || (len+tmplen > 150 && len > 20)){ if(fprintf(fp, "%s%s", NEWLINE, INDENTSTR) == EOF){ dprint((1, "write_single_abook_entry: fprintf indent failed\n")); return(EOF); } len = INDENT; } space = MAX(70 - len, 5); cur = extra_copy; end = cur + tmplen; while(cur < end){ if(end-cur > space){ int i; /* find first space after spot we want to break */ for(i = space; cur+i < end && cur[i] != SPACE; i++) ; cur[i] = '\0'; if(fputs(cur, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs extra failed\n")); return(EOF); } cur += (i+1); if(cur < end){ if(fprintf(fp, "%s%s", NEWLINE, INDENTXTRA) == EOF){ dprint((1, "write_single_abook_entry: fprintf indent failed\n")); return(EOF); } space = 70 - INDENT; } } else{ if(fputs(cur, fp) == EOF){ dprint((1, "write_single_abook_entry: fputs extra failed\n")); return(EOF); } cur = end; } } if(extra_copy) fs_give((void **)&extra_copy); } } else fcc_width = 0; fprintf(fp, "%s", NEWLINE); if(ret_nick_width) *ret_nick_width = nick_width; if(ret_full_width) *ret_full_width = full_width; if(ret_addr_width) *ret_addr_width = addr_width; if(ret_fcc_width) *ret_fcc_width = fcc_width; return(0); } char * backcompat_encoding_for_abook(char *buf1, size_t buf1len, char *buf2, size_t buf2len, char *srcstr) { char *encoded = NULL; char *p; int its_ascii = 1; for(p = srcstr; *p && its_ascii; p++) if(*p & 0x80) its_ascii = 0; /* if it is ascii, go with that */ if(its_ascii) encoded = srcstr; else{ char *trythischarset = NULL; /* * If it is possible to translate the UTF-8 * string into the user's character set then * do that. For backwards compatibility with * old pines. */ if(ps_global->keyboard_charmap && ps_global->keyboard_charmap[0]) trythischarset = ps_global->keyboard_charmap; else if(ps_global->display_charmap && ps_global->display_charmap[0]) trythischarset = ps_global->display_charmap; if(trythischarset){ SIZEDTEXT src, dst; src.data = (unsigned char *) srcstr; src.size = strlen(srcstr); memset(&dst, 0, sizeof(dst)); if(utf8_cstext(&src, trythischarset, &dst, 0)){ if(dst.data){ strncpy(buf1, (char *) dst.data, buf1len); buf1[buf1len-1] = '\0'; fs_give((void **) &dst.data); encoded = rfc1522_encode(buf2, buf2len, (unsigned char *) buf1, trythischarset); if(encoded) REPLACE_NEWLINES_WITH_SPACE(encoded); } } } if(!encoded){ encoded = rfc1522_encode(buf1, buf1len, (unsigned char *) srcstr, "UTF-8"); if(encoded) REPLACE_NEWLINES_WITH_SPACE(encoded); } } return(encoded); } int percent_abook_saved(void) { return((int)(((unsigned long)entry_num_for_percent * (unsigned long)100) / (unsigned long)tot_for_percent)); } /* * Free memory associated with entry ae. * * Args: ae -- Address book entry to be freed. */ void free_ae(AdrBk_Entry **ae) { if(!ae || !(*ae)) return; free_ae_parts(*ae); fs_give((void **) ae); } /* * Free memory associated with entry ae but not ae itself. */ void free_ae_parts(AdrBk_Entry *ae) { char **p; if(!ae) return; if(ae->nickname && ae->nickname != empty) fs_give((void **) &ae->nickname); if(ae->fullname && ae->fullname != empty) fs_give((void **) &ae->fullname); if(ae->tag == Single){ if(ae->addr.addr && ae->addr.addr != empty) fs_give((void **) &ae->addr.addr); } else if(ae->tag == List){ if(ae->addr.list){ for(p = ae->addr.list; *p; p++) if(*p != empty) fs_give((void **) p); fs_give((void **) &ae->addr.list); } } if(ae->fcc && ae->fcc != empty) fs_give((void **) &ae->fcc); if(ae->extra && ae->extra != empty) fs_give((void **) &ae->extra); defvalue_ae(ae); } /* * Inserts element new_ae before element put_it_before_this. */ void insert_ab_entry(AdrBk *ab, a_c_arg_t put_it_before_this, AdrBk_Entry *new_ae, int use_deleted_list) { adrbk_cntr_t before, old_count, new_count; AdrBk_Entry *ae_before, *ae_before_next; dprint((7, "- insert_ab_entry(before_this=%ld) -\n", (long) put_it_before_this)); before = (adrbk_cntr_t) put_it_before_this; if(!ab || before == NO_NEXT || (!use_deleted_list && before > ab->count) || (use_deleted_list && before > ab->del_count)){ ; } else{ /* * add space for new entry to array * slide entries [before ... old_count-1] down one * put new_ae into opened up slot */ old_count = use_deleted_list ? ab->del_count : ab->count; new_count = old_count + 1; if(old_count == 0){ if(use_deleted_list){ if(ab->del) /* shouldn't happen */ fs_give((void **) &ab->del); /* first entry in new array */ ab->del = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry)); defvalue_ae(ab->del); } else{ if(ab->arr) /* shouldn't happen */ fs_give((void **) &ab->arr); /* first entry in new array */ ab->arr = (AdrBk_Entry *) fs_get(new_count * sizeof(AdrBk_Entry)); defvalue_ae(ab->arr); } } else{ if(use_deleted_list){ fs_resize((void **) &ab->del, new_count * sizeof(AdrBk_Entry)); defvalue_ae(&ab->del[new_count-1]); } else{ fs_resize((void **) &ab->arr, new_count * sizeof(AdrBk_Entry)); defvalue_ae(&ab->arr[new_count-1]); } } if(use_deleted_list){ ab->del_count = new_count; ae_before = adrbk_get_delae(ab, (a_c_arg_t) before); if(ae_before){ if(before < old_count){ ae_before_next = adrbk_get_delae(ab, (a_c_arg_t) (before+1)); memmove(ae_before_next, ae_before, (old_count-before) * sizeof(AdrBk_Entry)); } memcpy(ae_before, new_ae, sizeof(AdrBk_Entry)); } } else{ ab->count = new_count; ae_before = adrbk_get_ae(ab, (a_c_arg_t) before); if(ae_before){ if(before < old_count){ ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1)); memmove(ae_before_next, ae_before, (old_count-before) * sizeof(AdrBk_Entry)); } memcpy(ae_before, new_ae, sizeof(AdrBk_Entry)); } } } if(!use_deleted_list) repair_abook_tries(ab); } /* * Moves element move_this_one before element put_it_before_this. */ void move_ab_entry(AdrBk *ab, a_c_arg_t move_this_one, a_c_arg_t put_it_before_this) { adrbk_cntr_t m, before; AdrBk_Entry ae_tmp; AdrBk_Entry *ae_m, *ae_m_next, *ae_before, *ae_before_prev, *ae_before_next; dprint((7, "- move_ab_entry(move_this=%ld,before_this=%ld) -\n", (long) move_this_one, (long) put_it_before_this)); m = (adrbk_cntr_t) move_this_one; before = (adrbk_cntr_t) put_it_before_this; if(!ab || m == NO_NEXT || before == NO_NEXT || m == before || m+1 == before || before > ab->count){ ; } else if(m+1 < before){ /* * copy m * slide entries [m+1 ... before-1] up one ("up" means smaller indices) * put m into opened up slot */ ae_m = adrbk_get_ae(ab, (a_c_arg_t) m); ae_m_next = adrbk_get_ae(ab, (a_c_arg_t) (m+1)); ae_before_prev = adrbk_get_ae(ab, (a_c_arg_t) (before-1)); if(ae_m && ae_m_next && ae_before_prev){ memcpy(&ae_tmp, ae_m, sizeof(ae_tmp)); memmove(ae_m, ae_m_next, (before-m-1) * sizeof(ae_tmp)); memcpy(ae_before_prev, &ae_tmp, sizeof(ae_tmp)); } } else if(m > before){ /* * copy m * slide entries [before ... m-1] down one * put m into opened up slot */ ae_m = adrbk_get_ae(ab, (a_c_arg_t) m); ae_before = adrbk_get_ae(ab, (a_c_arg_t) before); ae_before_next = adrbk_get_ae(ab, (a_c_arg_t) (before+1)); if(ae_m && ae_before && ae_before_next){ memcpy(&ae_tmp, ae_m, sizeof(ae_tmp)); memmove(ae_before_next, ae_before, (m-before) * sizeof(ae_tmp)); memcpy(ae_before, &ae_tmp, sizeof(ae_tmp)); } } dprint((9, "- move_ab_entry: done -\n")); repair_abook_tries(ab); } /* * Deletes element delete_this_one from in-core data structure. * If save_it is set the deleted entry is moved to the deleted_list. */ void delete_ab_entry(AdrBk *ab, a_c_arg_t delete_this_one, int save_it) { adrbk_cntr_t d; AdrBk_Entry *ae_deleted, *ae_deleted_next; dprint((7, "- delete_ab_entry(delete_this=%ld,save_it=%d) -\n", (long) delete_this_one, save_it)); d = (adrbk_cntr_t) delete_this_one; if(!ab || d == NO_NEXT || d >= ab->count){ ; } else{ /* * Move the entry to the deleted_list if asked to. */ ae_deleted = adrbk_get_ae(ab, (a_c_arg_t) d); if(ae_deleted){ if(save_it){ char *oldnick, *newnick; size_t len; struct tm *tm_now; time_t now; /* * First prepend the prefix * #DELETED-YY/MM/DD# * to the nickname. */ now = time((time_t *) NULL); tm_now = localtime(&now); oldnick = ae_deleted->nickname; len = strlen(oldnick) + DELETED_LEN + strlen("YY/MM/DD#"); newnick = (char *) fs_get((len+1) * sizeof(char)); snprintf(newnick, len+1, "%s%02d/%02d/%02d#%s", DELETED, (tm_now->tm_year)%100, tm_now->tm_mon+1, tm_now->tm_mday, oldnick ? oldnick : ""); newnick[len] = '\0'; if(ae_deleted->nickname && ae_deleted->nickname != empty) fs_give((void **) &ae_deleted->nickname); ae_deleted->nickname = newnick; /* * Now insert this entry in the deleted_list. */ insert_ab_entry(ab, (a_c_arg_t) ab->del_count, ae_deleted, 1); } else free_ae_parts(ae_deleted); } /* * slide entries [deleted+1 ... count-1] up one */ if(d+1 < ab->count){ ae_deleted = adrbk_get_ae(ab, (a_c_arg_t) d); ae_deleted_next = adrbk_get_ae(ab, (a_c_arg_t) (d+1)); if(ae_deleted && ae_deleted_next) memmove(ae_deleted, ae_deleted_next, (ab->count-d-1) * sizeof(AdrBk_Entry)); } ab->count--; if(ab->count > 0) fs_resize((void **) &ab->arr, ab->count * sizeof(AdrBk_Entry)); else fs_give((void **) &ab->arr); } repair_abook_tries(ab); } /* * We may want to be smarter about this and repair instead of * rebuild these. */ void repair_abook_tries(AdrBk *ab) { if(ab->arr){ AdrBk_Trie *save_nick_trie, *save_addr_trie, *save_full_trie, *save_revfull_trie; save_nick_trie = ab->nick_trie; ab->nick_trie = NULL; save_addr_trie = ab->addr_trie; ab->addr_trie = NULL; save_full_trie = ab->full_trie; ab->full_trie = NULL; save_revfull_trie = ab->revfull_trie; ab->revfull_trie = NULL; if(build_abook_tries(ab, NULL)){ dprint((2, "trouble rebuilding tries, restoring\n")); if(ab->nick_trie) free_abook_trie(&ab->nick_trie); /* better than nothing */ ab->nick_trie = save_nick_trie; if(ab->addr_trie) free_abook_trie(&ab->addr_trie); ab->addr_trie = save_addr_trie; if(ab->full_trie) free_abook_trie(&ab->full_trie); ab->full_trie = save_full_trie; if(ab->revfull_trie) free_abook_trie(&ab->revfull_trie); ab->revfull_trie = save_revfull_trie; } else{ if(save_nick_trie) free_abook_trie(&save_nick_trie); if(save_addr_trie) free_abook_trie(&save_addr_trie); if(save_full_trie) free_abook_trie(&save_full_trie); if(save_revfull_trie) free_abook_trie(&save_revfull_trie); } } } /* * Free the list of distribution lists which have been expanded. * Leaves the head of the list alone. * * Args: exp_head -- Head of the expanded list. */ void exp_free(EXPANDED_S *exp_head) { EXPANDED_S *e, *the_next_one; e = exp_head ? exp_head->next : NULL; if(!e) return; while(e){ the_next_one = e->next; fs_give((void **)&e); e = the_next_one; } exp_head->next = (EXPANDED_S *)NULL; } /* * Is entry n expanded? * * Args: exp_head -- Head of the expanded list. * n -- The entry num to check */ int exp_is_expanded(EXPANDED_S *exp_head, a_c_arg_t n) { register EXPANDED_S *e; adrbk_cntr_t nn; nn = (adrbk_cntr_t)n; e = exp_head ? exp_head->next : NULL; /* * The list is kept ordered, so we search until we find it or are * past it. */ while(e){ if(e->ent >= nn) break; e = e->next; } return(e && e->ent == nn); } /* * How many entries expanded in this addrbook. * * Args: exp_head -- Head of the expanded list. */ int exp_howmany_expanded(EXPANDED_S *exp_head) { register EXPANDED_S *e; int cnt = 0; e = exp_head ? exp_head->next : NULL; while(e){ cnt++; e = e->next; } return(cnt); } /* * Are any entries expanded? * * Args: exp_head -- Head of the expanded list. */ int exp_any_expanded(EXPANDED_S *exp_head) { return(exp_head && exp_head->next != NULL); } /* * Return next entry num in list. * * Args: cur -- Current position in the list. * * Result: Returns the number of the next entry, or NO_NEXT if there is * no next entry. As a side effect, the cur pointer is incremented. */ adrbk_cntr_t exp_get_next(EXPANDED_S **cur) { adrbk_cntr_t ret = NO_NEXT; if(cur && *cur && (*cur)->next){ ret = (*cur)->next->ent; *cur = (*cur)->next; } return(ret); } /* * Mark entry n as being expanded. * * Args: exp_head -- Head of the expanded list. * n -- The entry num to mark */ void exp_set_expanded(EXPANDED_S *exp_head, a_c_arg_t n) { register EXPANDED_S *e; EXPANDED_S *new; adrbk_cntr_t nn; nn = (adrbk_cntr_t)n; if(!exp_head) alpine_panic("exp_head not set in exp_set_expanded"); for(e = exp_head; e->next; e = e->next) if(e->next->ent >= nn) break; if(e->next && e->next->ent == nn) /* already there */ return; /* add new after e */ new = (EXPANDED_S *)fs_get(sizeof(EXPANDED_S)); new->ent = nn; new->next = e->next; e->next = new; } /* * Mark entry n as being *not* expanded. * * Args: exp_head -- Head of the expanded list. * n -- The entry num to mark */ void exp_unset_expanded(EXPANDED_S *exp_head, a_c_arg_t n) { register EXPANDED_S *e; EXPANDED_S *delete_this_one = NULL; adrbk_cntr_t nn; nn = (adrbk_cntr_t)n; if(!exp_head) alpine_panic("exp_head not set in exp_unset_expanded"); for(e = exp_head; e->next; e = e->next) if(e->next->ent >= nn) break; if(e->next && e->next->ent == nn){ delete_this_one = e->next; e->next = e->next->next; } if(delete_this_one) fs_give((void **)&delete_this_one); } /* * Adjust the "expanded" list to correspond to addrbook entry n being * deleted. * * Args: exp_head -- Head of the expanded list. * n -- The entry num being deleted */ void exp_del_nth(EXPANDED_S *exp_head, a_c_arg_t n) { register EXPANDED_S *e; int delete_when_done = 0; adrbk_cntr_t nn; nn = (adrbk_cntr_t)n; if(!exp_head) alpine_panic("exp_head not set in exp_del_nth"); e = exp_head->next; while(e && e->ent < nn) e = e->next; if(e){ if(e->ent == nn){ delete_when_done++; e = e->next; } while(e){ e->ent--; /* adjust entry nums */ e = e->next; } if(delete_when_done) exp_unset_expanded(exp_head, n); } } /* * Adjust the "expanded" list to correspond to a new addrbook entry being * added between current entries n-1 and n. * * Args: exp_head -- Head of the expanded list. * n -- The entry num being added * * The new entry is not marked expanded. */ void exp_add_nth(EXPANDED_S *exp_head, a_c_arg_t n) { register EXPANDED_S *e; adrbk_cntr_t nn; nn = (adrbk_cntr_t)n; if(!exp_head) alpine_panic("exp_head not set in exp_add_nth"); e = exp_head->next; while(e && e->ent < nn) e = e->next; while(e){ e->ent++; /* adjust entry nums */ e = e->next; } } static AdrBk *ab_for_sort; /* * Compare two address book entries. Args are AdrBk_Entry **'s. * Sorts lists after simple addresses and then sorts on Fullname field. */ int cmp_ae_by_full_lists_last(const qsort_t *a, const qsort_t *b) { AdrBk_Entry **x = (AdrBk_Entry **)a, **y = (AdrBk_Entry **)b; int result; if((*x)->tag == List && (*y)->tag == Single) result = 1; else if((*x)->tag == Single && (*y)->tag == List) result = -1; else{ register char *p, *q, *r, *s; p = (*x)->fullname; if(*p == '"' && *(p+1)) p++; q = (*y)->fullname; if(*q == '"' && *(q+1)) q++; r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, p); s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000, SIZEOF_20KBUF-10000, q); result = (*pcollator)(r, s); if(result == 0) result = (*pcollator)((*x)->nickname, (*y)->nickname); } return(result); } /* * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s). * Sorts lists after simple addresses and then sorts on Fullname field. */ int cmp_cntr_by_full_lists_last(const qsort_t *a, const qsort_t *b) { adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */ *y = (adrbk_cntr_t *)b; AdrBk_Entry *x_ae, *y_ae; if(ps_global->intr_pending) longjmp(jump_over_qsort, 1); ALARM_BLIP(); x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x)); y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y)); return(cmp_ae_by_full_lists_last((const qsort_t *) &x_ae, (const qsort_t *) &y_ae)); } /* * Compare two address book entries. Args are AdrBk_Entry **'s. * Sorts on Fullname field. */ int cmp_ae_by_full(const qsort_t *a, const qsort_t *b) { AdrBk_Entry **x = (AdrBk_Entry **)a, **y = (AdrBk_Entry **)b; int result; register char *p, *q, *r, *s; p = (*x)->fullname; if(*p == '"' && *(p+1)) p++; q = (*y)->fullname; if(*q == '"' && *(q+1)) q++; r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, p); s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000, SIZEOF_20KBUF-10000, q); result = (*pcollator)(r, s); if(result == 0) result = (*pcollator)((*x)->nickname, (*y)->nickname); return(result); } /* * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s). * Sorts on Fullname field. */ int cmp_cntr_by_full(const qsort_t *a, const qsort_t *b) { adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */ *y = (adrbk_cntr_t *)b; AdrBk_Entry *x_ae, *y_ae; if(ps_global->intr_pending) longjmp(jump_over_qsort, 1); ALARM_BLIP(); x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x)); y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y)); return(cmp_ae_by_full((const qsort_t *) &x_ae, (const qsort_t *) &y_ae)); } /* * Compare two address book entries. Args are AdrBk_Entry **'s. * Sorts lists after simple addresses and then sorts on Nickname field. */ int cmp_ae_by_nick_lists_last(const qsort_t *a, const qsort_t *b) { AdrBk_Entry **x = (AdrBk_Entry **)a, **y = (AdrBk_Entry **)b; int result; if((*x)->tag == List && (*y)->tag == Single) result = 1; else if((*x)->tag == Single && (*y)->tag == List) result = -1; else result = (*pcollator)((*x)->nickname, (*y)->nickname); return(result); } /* * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s). * Sorts lists after simple addresses and then sorts on Nickname field. */ int cmp_cntr_by_nick_lists_last(const qsort_t *a, const qsort_t *b) { adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */ *y = (adrbk_cntr_t *)b; AdrBk_Entry *x_ae, *y_ae; if(ps_global->intr_pending) longjmp(jump_over_qsort, 1); ALARM_BLIP(); x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x)); y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y)); return(cmp_ae_by_nick_lists_last((const qsort_t *) &x_ae, (const qsort_t *) &y_ae)); } /* * Compare two address book entries. Args are AdrBk_Entry **'s. * Sorts on Nickname field. */ int cmp_ae_by_nick(const qsort_t *a, const qsort_t *b) { AdrBk_Entry **x = (AdrBk_Entry **)a, **y = (AdrBk_Entry **)b; return((*pcollator)((*x)->nickname, (*y)->nickname)); } /* * Compare two address book entries. Args are adrbk_cntr_t *'s (element #'s). * Sorts on Nickname field. */ int cmp_cntr_by_nick(const qsort_t *a, const qsort_t *b) { adrbk_cntr_t *x = (adrbk_cntr_t *)a, /* *x is an element_number */ *y = (adrbk_cntr_t *)b; AdrBk_Entry *x_ae, *y_ae; if(ps_global->intr_pending) longjmp(jump_over_qsort, 1); ALARM_BLIP(); x_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*x)); y_ae = adrbk_get_ae(ab_for_sort, (a_c_arg_t)(*y)); return(cmp_ae_by_nick((const qsort_t *) &x_ae, (const qsort_t *) &y_ae)); } /* * For sorting a simple list of pointers to addresses (skip initial quotes) */ int cmp_addr(const qsort_t *a1, const qsort_t *a2) { char *x = *(char **)a1, *y = *(char **)a2; char *r, *s; if(x && *x == '"') x++; if(y && *y == '"') y++; r = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, x); s = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf+10000, SIZEOF_20KBUF-10000, y); return((*pcollator)(r, s)); } /* * Sort an array of strings, except skip initial quotes. */ void sort_addr_list(char **list) { register char **p; /* find size of list */ for(p = list; *p != NULL; p++) ;/* do nothing */ qsort((qsort_t *)list, (size_t)(p - list), sizeof(char *), cmp_addr); } /* * Sort this address book. * * Args: ab -- address book to sort * current_entry_num -- see next description * new_entry_num -- return new entry_num of current_entry_num here * * Result: return code: 0 all went well * -2 error writing address book, check errno * * The sorting strategy is to allocate an array of length ab->count which * contains the element numbers 0, 1, ..., ab->count - 1, representing the * entries in the addrbook, of course. Sort the array, then use that * result to swap ae's in the in-core addrbook array before writing it out. */ int adrbk_sort(AdrBk *ab, a_c_arg_t current_entry_num, adrbk_cntr_t *new_entry_num, int be_quiet) { adrbk_cntr_t *sort_array, *inv, tmp; long i, j, hi, count; int skip_the_sort = 0, we_cancel = 0, we_turned_on = 0; AdrBk_Entry ae_tmp, *ae_i, *ae_hi; EXPANDED_S *e, *e2, *smallest; dprint((5, "- adrbk_sort -\n")); count = (long) (ab->count); if(!ab) return -2; if(ab->sort_rule == AB_SORT_RULE_NONE) return 0; if(count < 2) return 0; sort_array = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t)); inv = (adrbk_cntr_t *) fs_get(count * sizeof(adrbk_cntr_t)); for(i = 0L; i < count; i++) sort_array[i] = (adrbk_cntr_t) i; ab_for_sort = ab; if(setjmp(jump_over_qsort)) skip_the_sort = 1; if(!skip_the_sort){ we_turned_on = intr_handling_on(); if(!be_quiet) we_cancel = busy_cue(_("Sorting address book"), NULL, 0); qsort((qsort_t *)sort_array, (size_t)count, sizeof(adrbk_cntr_t), (ab->sort_rule == AB_SORT_RULE_FULL_LISTS) ? cmp_cntr_by_full_lists_last : (ab->sort_rule == AB_SORT_RULE_FULL) ? cmp_cntr_by_full : (ab->sort_rule == AB_SORT_RULE_NICK_LISTS) ? cmp_cntr_by_nick_lists_last : /* (ab->sort_rule == AB_SORT_RULE_NICK) */ cmp_cntr_by_nick); } dprint((9, "- adrbk_sort: done with first sort -\n")); if(we_turned_on) intr_handling_off(); if(skip_the_sort){ q_status_message(SM_ORDER, 3, 3, _("Address book sort cancelled, using old order for now")); goto skip_the_write_too; } dprint((5, "- adrbk_sort (%s)\n", ab->sort_rule==AB_SORT_RULE_FULL_LISTS ? "FullListsLast" : ab->sort_rule==AB_SORT_RULE_FULL ? "Fullname" : ab->sort_rule==AB_SORT_RULE_NICK_LISTS ? "NickListLast" : ab->sort_rule==AB_SORT_RULE_NICK ? "Nickname" : "unknown")); /* * Rearrange the in-core array of ae's to be in the new order. * We can do that by sorting the inverse into the correct order in parallel. */ for(i = 0L; i < count; i++) inv[sort_array[i]] = i; if(new_entry_num && (adrbk_cntr_t) current_entry_num >= 0 && (adrbk_cntr_t) current_entry_num < count){ *new_entry_num = inv[(adrbk_cntr_t) current_entry_num]; } /* * The expanded and selected lists will be wrong now. Correct them. * First the expanded list. */ e = ab->exp ? ab->exp->next : NULL; while(e){ if(e->ent >= 0 && e->ent < count) e->ent = inv[e->ent]; e = e->next; } /* * And sort into ascending order as expected by the exp_ routines. */ e = ab->exp ? ab->exp->next : NULL; while(e){ /* move smallest to e */ e2 = e; smallest = e; while(e2){ if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent) smallest = e2; e2 = e2->next; } /* swap values in e and smallest */ if(e != smallest){ tmp = e->ent; e->ent = smallest->ent; smallest->ent = tmp; } e = e->next; } /* * Same thing for the selected list. */ e = ab->selects ? ab->selects->next : NULL; while(e){ if(e->ent >= 0 && e->ent < count) e->ent = inv[e->ent]; e = e->next; } e = ab->selects ? ab->selects->next : NULL; while(e){ /* move smallest to e */ e2 = e; smallest = e; while(e2){ if(e2->ent != NO_NEXT && e2->ent >= 0 && e2->ent < count && e2->ent < smallest->ent) smallest = e2; e2 = e2->next; } /* swap values in e and smallest */ if(e != smallest){ tmp = e->ent; e->ent = smallest->ent; smallest->ent = tmp; } e = e->next; } for(i = 0L; i < count; i++){ if(i != inv[i]){ /* find inv[j] which = i */ for(j = i+1; j < count; j++){ if(i == inv[j]){ hi = j; break; } } /* swap i and hi */ ae_i = adrbk_get_ae(ab, (a_c_arg_t) i); ae_hi = adrbk_get_ae(ab, (a_c_arg_t) hi); if(ae_i && ae_hi){ memcpy(&ae_tmp, ae_i, sizeof(ae_tmp)); memcpy(ae_i, ae_hi, sizeof(ae_tmp)); memcpy(ae_hi, &ae_tmp, sizeof(ae_tmp)); } /* else can't happen */ inv[hi] = inv[i]; } } if(we_cancel) cancel_busy_cue(0); repair_abook_tries(ab); dprint((9, "- adrbk_sort: done with rearranging -\n")); skip_the_write_too: if(we_cancel) cancel_busy_cue(0); if(sort_array) fs_give((void **) &sort_array); if(inv) fs_give((void **) &inv); return 0; } /* * Returns 1 if any addrbooks are in Open state, 0 otherwise. * * This is a test for ostatus == Open, not for whether or not the address book * FILE is opened. */ int any_ab_open(void) { int i, ret = 0; if(as.initialized) for(i = 0; ret == 0 && i < as.n_addrbk; i++) if(as.adrbks[i].ostatus == Open) ret++; return(ret); } /* * Make sure addrbooks are minimally initialized. */ void init_ab_if_needed(void) { dprint((9, "- init_ab_if_needed -\n")); if(!as.initialized) (void)init_addrbooks(Closed, 0, 0, 1); } /* * Sets everything up to get started. * * Args: want_status -- The desired OpenStatus for all addrbooks. * reset_to_top -- Forget about the old location and put cursor * at top. * open_if_only_one -- If want_status is HalfOpen and there is only * section to look at, then promote want_status * to Open. * ro_warning -- Set ReadOnly warning global * * Return: 1 if ok, 0 if problem */ int init_addrbooks(OpenStatus want_status, int reset_to_top, int open_if_only_one, int ro_warning) { register PerAddrBook *pab; char *q, **t; long line; dprint((4, "-- init_addrbooks(%s, %d, %d, %d) --\n", want_status==Open ? "Open" : want_status==HalfOpen ? "HalfOpen" : want_status==ThreeQuartOpen ? "ThreeQuartOpen" : want_status==NoDisplay ? "NoDisplay" : "Closed", reset_to_top, open_if_only_one, ro_warning)); as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global) - HEADER_ROWS(ps_global); if(as.l_p_page <= 0) as.no_op_possbl++; else as.no_op_possbl = 0; as.ro_warning = ro_warning; /* already been initialized */ if(as.initialized){ int i; /* * Special case. If there is only one addressbook we start the * user out with that open. */ if(want_status == HalfOpen && ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) || (F_ON(F_EXPANDED_ADDRBOOKS, ps_global) && F_ON(F_CMBND_ABOOK_DISP, ps_global)))) want_status = Open; /* open to correct state */ for(i = 0; i < as.n_addrbk; i++) init_abook(&as.adrbks[i], want_status); if(reset_to_top){ warp_to_beginning(); as.top_ent = 0L; line = first_selectable_line(0L); if(line == NO_LINE) as.cur_row = 0L; else as.cur_row = line; if(as.cur_row >= as.l_p_page) as.top_ent += (as.cur_row - as.l_p_page + 1); as.old_cur_row = as.cur_row; } dprint((4, "init_addrbooks: already initialized: %d books\n", as.n_addrbk)); return((as.n_addrbk + as.n_serv) ? 1 : 0); } as.initialized = 1; /* count directory servers */ as.n_serv = 0; as.n_impl = 0; #ifdef ENABLE_LDAP if(ps_global->VAR_LDAP_SERVERS && ps_global->VAR_LDAP_SERVERS[0] && ps_global->VAR_LDAP_SERVERS[0][0]) for(t = ps_global->VAR_LDAP_SERVERS; t[0] && t[0][0]; t++){ LDAP_SERV_S *info; as.n_serv++; info = break_up_ldap_server(*t); as.n_impl += (info && info->impl) ? 1 : 0; if(info) free_ldap_server_info(&info); } #endif /* ENABLE_LDAP */ /* count addressbooks */ as.how_many_personals = 0; if(ps_global->VAR_ADDRESSBOOK && ps_global->VAR_ADDRESSBOOK[0] && ps_global->VAR_ADDRESSBOOK[0][0]) for(t = ps_global->VAR_ADDRESSBOOK; t[0] && t[0][0]; t++) as.how_many_personals++; as.n_addrbk = as.how_many_personals; if(ps_global->VAR_GLOB_ADDRBOOK && ps_global->VAR_GLOB_ADDRBOOK[0] && ps_global->VAR_GLOB_ADDRBOOK[0][0]) for(t = ps_global->VAR_GLOB_ADDRBOOK; t[0] && t[0][0]; t++) as.n_addrbk++; if(want_status == HalfOpen && ((open_if_only_one && as.n_addrbk == 1 && as.n_serv == 0) || (F_ON(F_EXPANDED_ADDRBOOKS, ps_global) && F_ON(F_CMBND_ABOOK_DISP, ps_global)))) want_status = Open; /* * allocate array of PerAddrBooks * (we don't give this up until we exit Pine, but it's small) */ if(as.n_addrbk){ as.adrbks = (PerAddrBook *)fs_get(as.n_addrbk * sizeof(PerAddrBook)); memset((void *)as.adrbks, 0, as.n_addrbk * sizeof(PerAddrBook)); /* init PerAddrBook data */ for(as.cur = 0; as.cur < as.n_addrbk; as.cur++){ char *nickname = NULL, *filename = NULL; if(as.cur < as.how_many_personals) q = ps_global->VAR_ADDRESSBOOK[as.cur]; else q = ps_global->VAR_GLOB_ADDRBOOK[as.cur - as.how_many_personals]; pab = &as.adrbks[as.cur]; /* Parse entry for optional nickname and filename */ get_pair(q, &nickname, &filename, 0, 0); if(nickname && !*nickname) fs_give((void **)&nickname); strncpy(tmp_20k_buf, filename, SIZEOF_20KBUF); fs_give((void **)&filename); filename = tmp_20k_buf; if(nickname == NULL) pab->abnick = cpystr(filename); else pab->abnick = nickname; if(*filename == '~') fnexpand(filename, SIZEOF_20KBUF); if(*filename == '{' || is_absolute_path(filename)){ pab->filename = cpystr(filename); /* fully qualified */ } else{ char book_path[MAXPATH+1]; char *lc = last_cmpnt(ps_global->pinerc); book_path[0] = '\0'; if(lc != NULL){ strncpy(book_path, ps_global->pinerc, MIN(lc - ps_global->pinerc, sizeof(book_path)-1)); book_path[MIN(lc - ps_global->pinerc, sizeof(book_path)-1)] = '\0'; } strncat(book_path, filename, sizeof(book_path)-1-strlen(book_path)); pab->filename = cpystr(book_path); } if(*pab->filename == '{') pab->type |= REMOTE_VIA_IMAP; if(as.cur >= as.how_many_personals) pab->type |= GLOBAL; if(ps_global->mail_stream && ps_global->mail_stream->lock && (pab->type & REMOTE_VIA_IMAP)){ as.initialized = 0; pab->access = NoAccess; } else{ pab->access = adrbk_access(pab); } /* global address books are forced readonly */ if(pab->type & GLOBAL && pab->access != NoAccess) pab->access = ReadOnly; pab->ostatus = TotallyClosed; /* * and remember that the memset above initializes everything * else to 0 */ init_abook(pab, want_status); } } /* * Have to reset_to_top in this case since this is the first open, * regardless of the value of the argument, since these values haven't been * set before here. */ as.cur = 0; as.top_ent = 0L; warp_to_beginning(); line = first_selectable_line(0L); if(line == NO_LINE) as.cur_row = 0L; else as.cur_row = line; if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = as.l_p_page - 1; } as.old_cur_row = as.cur_row; return((as.n_addrbk + as.n_serv) ? 1 : 0); } /* * Something was changed in options screen, so need to start over. */ void addrbook_reset(void) { dprint((4, "- addrbook_reset -\n")); completely_done_with_adrbks(); } /* * Sort was changed in options screen. Since we only sort normally * when we actually make a change to the address book, we need to * go out of our way to sort here. */ void addrbook_redo_sorts(void) { int i; PerAddrBook *pab; AdrBk *ab; dprint((4, "- addrbook_redo_sorts -\n")); addrbook_reset(); init_ab_if_needed(); for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; init_abook(pab, NoDisplay); ab = pab->address_book; if(!adrbk_is_in_sort_order(ab, 0)) adrbk_write(ab, 0, NULL, NULL, 1, 0); } addrbook_reset(); } /* * Returns type of access allowed on this addrbook. */ AccessType adrbk_access(PerAddrBook *pab) { char fbuf[MAXPATH+1]; AccessType access = NoExists; CONTEXT_S *dummy_cntxt = NULL; dprint((9, "- addrbook_access -\n")); if(pab && pab->type & REMOTE_VIA_IMAP){ /* * Open_fcc creates the folder if it didn't already exist. */ if((pab->so = open_fcc(pab->filename, &dummy_cntxt, 1, "Error: ", " Can't fetch remote addrbook.")) != NULL){ /* * We know the folder is there but don't know what access * rights we have until we try to select it, which we don't * want to do unless we have to. So delay evaluating. */ access = MaybeRorW; } } else if(pab){ /* local file */ #if defined(NO_LOCAL_ADDRBOOKS) /* don't allow any access to local addrbooks */ access = NoAccess; #else /* !NO_LOCAL_ADDRBOOKS) */ build_path(fbuf, is_absolute_path(pab->filename) ? NULL : ps_global->home_dir, pab->filename, sizeof(fbuf)); #if defined(DOS) || defined(OS2) /* * Microsoft networking causes some access calls to do a DNS query (!!) * when it is turned on. In particular, if there is a / in the filename * this seems to happen. So, just don't allow it. */ if(strindex(fbuf, '/') != NULL){ dprint((2, "\"/\" not allowed in addrbook name\n")); return NoAccess; } #else /* !DOS */ /* also prevent backslash in non-DOS addrbook names */ if(strindex(fbuf, '\\') != NULL){ dprint((2, "\"\\\" not allowed in addrbook name\n")); return NoAccess; } #endif /* !DOS */ if(can_access(fbuf, ACCESS_EXISTS) == 0){ if(can_access(fbuf, EDIT_ACCESS) == 0){ char *dir, *p; dir = "."; if((p = last_cmpnt(fbuf)) != NULL){ *--p = '\0'; dir = *fbuf ? fbuf : "/"; } #if defined(DOS) || defined(OS2) /* * If the dir has become a drive letter and : (e.g. "c:") * then append a "\". The library function access() in the * win 16 version of MSC seems to require this. */ if(isalpha((unsigned char) *dir) && *(dir+1) == ':' && *(dir+2) == '\0'){ *(dir+2) = '\\'; *(dir+3) = '\0'; } #endif /* DOS || OS2 */ /* * Even if we can edit the address book file itself, we aren't * going to be able to change it unless we can also write in * the directory that contains it (because we write into a * temp file and then rename). */ if(can_access(dir, EDIT_ACCESS) == 0) access = ReadWrite; else{ access = ReadOnly; q_status_message1(SM_ORDER, 2, 2, "Address book directory (%.200s) is ReadOnly", dir); } } else if(can_access(fbuf, READ_ACCESS) == 0) access = ReadOnly; else access = NoAccess; } #endif /* !NO_LOCAL_ADDRBOOKS) */ } return(access); } /* * Trim back remote address books if necessary. */ void trim_remote_adrbks(void) { register PerAddrBook *pab; int i; dprint((2, "- trim_remote_adrbks -\n")); if(!as.initialized) return; for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->ostatus != TotallyClosed && pab->address_book && pab->address_book->rd) rd_trim_remdata(&pab->address_book->rd); } } /* * Free and close everything. */ void completely_done_with_adrbks(void) { register PerAddrBook *pab; int i; dprint((2, "- completely_done_with_adrbks -\n")); ab_nesting_level = 0; if(!as.initialized) return; for(i = 0; i < as.n_addrbk; i++) init_abook(&as.adrbks[i], TotallyClosed); for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->filename) fs_give((void **)&pab->filename); if(pab->abnick) fs_give((void **)&pab->abnick); } done_with_dlc_cache(); if(as.adrbks) fs_give((void **)&as.adrbks); as.n_addrbk = 0; as.initialized = 0; } /* * Initialize or re-initialize this address book. * * Args: pab -- the PerAddrBook ptr * want_status -- desired OpenStatus for this address book */ void init_abook(PerAddrBook *pab, OpenStatus want_status) { register OpenStatus new_status; dprint((4, "- init_abook -\n")); dprint((7, " addrbook nickname = %s filename = %s", pab->abnick ? pab->abnick : "", pab->filename ? pab->filename : "")); dprint((7, " ostatus was %s, want %s\n", pab->ostatus==Open ? "Open" : pab->ostatus==HalfOpen ? "HalfOpen" : pab->ostatus==ThreeQuartOpen ? "ThreeQuartOpen" : pab->ostatus==NoDisplay ? "NoDisplay" : pab->ostatus==Closed ? "Closed" : "TotallyClosed", want_status==Open ? "Open" : want_status==HalfOpen ? "HalfOpen" : want_status==ThreeQuartOpen ? "ThreeQuartOpen" : want_status==NoDisplay ? "NoDisplay" : want_status==Closed ? "Closed" : "TotallyClosed")); new_status = want_status; /* optimistic default */ if(want_status == TotallyClosed){ if(pab->address_book != NULL){ adrbk_close(pab->address_book); pab->address_book = NULL; } if(pab->so != NULL){ so_give(&pab->so); pab->so = NULL; } } /* * If we don't need it, release some addrbook memory by calling * adrbk_partial_close(). */ else if((want_status == Closed || want_status == HalfOpen) && pab->address_book != NULL){ adrbk_partial_close(pab->address_book); } /* If we want the addrbook read in and it hasn't been, do so */ else if(want_status == Open || want_status == NoDisplay){ if(pab->address_book == NULL){ /* abook handle is not currently active */ if(pab->access != NoAccess){ char warning[800]; /* place to put a warning */ int sort_rule; warning[0] = '\0'; if(pab->access == ReadOnly) sort_rule = AB_SORT_RULE_NONE; else sort_rule = ps_global->ab_sort_rule; pab->address_book = adrbk_open(pab, ps_global->home_dir, warning, sizeof(warning), sort_rule); if(pab->address_book == NULL){ pab->access = NoAccess; if(want_status == Open){ new_status = HalfOpen; /* best we can do */ q_status_message1(SM_ORDER | SM_DING, *warning?1:3, 4, _("Error opening/creating address book %.200s"), pab->abnick); if(*warning) q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s", as.n_addrbk > 1 ? pab->abnick : "addressbook", warning); } else new_status = Closed; dprint((1, "Error opening address book %s: %s\n", pab->abnick ? pab->abnick : "?", error_description(errno))); } else{ if(pab->access == NoExists) pab->access = ReadWrite; if(pab->access == ReadWrite){ /* * Add forced entries if there are any. These are * entries that are always supposed to show up in * personal address books. They're specified in the * global config file. */ add_forced_entries(pab->address_book); } new_status = want_status; dprint((2, "Address book %s (%s) opened with %ld items\n", pab->abnick ? pab->abnick : "?", pab->filename ? pab->filename : "?", (long)adrbk_count(pab->address_book))); if(*warning){ dprint((1, "Addressbook parse error in %s (%s): %s\n", pab->abnick ? pab->abnick : "?", pab->filename ? pab->filename : "?", warning)); if(!pab->gave_parse_warnings && want_status == Open){ pab->gave_parse_warnings++; q_status_message2(SM_ORDER, 3, 4, "%.200s: %.200s", as.n_addrbk > 1 ? pab->abnick : "addressbook", warning); } } } } else{ if(want_status == Open){ new_status = HalfOpen; /* best we can do */ q_status_message1(SM_ORDER | SM_DING, 3, 4, "Insufficient permissions for opening address book %.200s", pab->abnick); } else new_status = Closed; } } /* * File handle was already open but we were in Closed or HalfOpen * state. Check to see if someone else has changed the addrbook * since we first opened it. */ else if((pab->ostatus == Closed || pab->ostatus == HalfOpen) && ps_global->remote_abook_validity > 0) (void)adrbk_check_and_fix(pab, 1, 0, 0); } pab->ostatus = new_status; } /* * Does a validity check on all the open address books. * * Return -- number of address books with invalid data */ int adrbk_check_all_validity_now(void) { int i; int something_out_of_date = 0; PerAddrBook *pab; dprint((7, "- adrbk_check_all_validity_now -\n")); if(as.initialized){ for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->address_book){ adrbk_check_validity(pab->address_book, 1L); if(pab->address_book->flags & FILE_OUTOFDATE || (pab->address_book->rd && pab->address_book->rd->flags & REM_OUTOFDATE)) something_out_of_date++; } } } return(something_out_of_date); } /* * Fix out-of-date address book. This only fixes the AdrBk part of the * problem, not the pab and display. It closes and reopens the address_book * file, clears the cache, reads new data. * * Arg pab -- Pointer to the PerAddrBook data for this addrbook * safe -- It is safe to apply the fix * low_freq -- This is a low frequency check (longer between checks) * * Returns non-zero if addrbook was fixed */ int adrbk_check_and_fix(PerAddrBook *pab, int safe, int low_freq, int check_now) { int ret = 0; long chk_interval; if(!as.initialized || !pab) return(ret); dprint((7, "- adrbk_check_and_fix(%s) -\n", pab->filename ? pab->filename : "?")); if(pab->address_book){ if(check_now) chk_interval = 1L; else if(low_freq) chk_interval = -60L * MAX(LOW_FREQ_CHK_INTERVAL, ps_global->remote_abook_validity + 5); else chk_interval = 0L; adrbk_check_validity(pab->address_book, chk_interval); if(pab->address_book->flags & FILE_OUTOFDATE || (pab->address_book->rd && pab->address_book->rd->flags & REM_OUTOFDATE && !(pab->address_book->rd->flags & USER_SAID_NO))){ if(safe){ OpenStatus save_status; int save_rem_abook_valid = 0; dprint((2, "adrbk_check_and_fix %s: fixing %s\n", debug_time(0,0,ps_global->signal_in_progress), pab->filename ? pab->filename : "?")); if(ab_nesting_level > 0){ q_status_message3(SM_ORDER, 0, 2, "Resyncing address book%.200s%.200s%.200s", as.n_addrbk > 1 ? " \"" : "", as.n_addrbk > 1 ? pab->abnick : "", as.n_addrbk > 1 ? "\"" : ""); display_message('x'); fflush(stdout); } ret++; save_status = pab->ostatus; /* don't do the trim right now */ if(pab->address_book->rd) pab->address_book->rd->flags &= ~DO_REMTRIM; /* * Have to change this from -1 or we won't actually do * the resync. */ if((pab->address_book->rd && pab->address_book->rd->flags & REM_OUTOFDATE) && ps_global->remote_abook_validity == -1){ save_rem_abook_valid = -1; ps_global->remote_abook_validity = 0; } init_abook(pab, TotallyClosed); pab->so = NULL; /* this sets up pab->so, so is important */ pab->access = adrbk_access(pab); /* * If we just re-init to HalfOpen... we won't actually * open the address book, which was open before. That * would be fine but it's a little nicer if we can open * it now so that we don't defer the resync until * the next open, which would be a user action for sure. * Right now we may be here during a newmail check * timeout, so this is a good time to do the resync. */ if(save_status == HalfOpen || save_status == ThreeQuartOpen || save_status == Closed) init_abook(pab, NoDisplay); init_abook(pab, save_status); if(save_rem_abook_valid) ps_global->remote_abook_validity = save_rem_abook_valid; if(ab_nesting_level > 0 && pab->ostatus == save_status) q_status_message3(SM_ORDER, 0, 2, "Resynced address book%.200s%.200s%.200s", as.n_addrbk > 1 ? " \"" : "", as.n_addrbk > 1 ? pab->abnick : "", as.n_addrbk > 1 ? "\"" : ""); } else dprint((2, "adrbk_check_and_fix: not safe to fix %s\n", pab->filename ? pab->filename : "?")); } } return(ret); } static time_t last_check_and_fix_all; /* * Fix out of date address books. This only fixes the AdrBk part of the * problem, not the pab and display. It closes and reopens the address_book * files, clears the caches, reads new data. * * Args safe -- It is safe to apply the fix * low_freq -- This is a low frequency check (longer between checks) * * Returns non-zero if an addrbook was fixed */ int adrbk_check_and_fix_all(int safe, int low_freq, int check_now) { int i, ret = 0; PerAddrBook *pab; if(!as.initialized) return(ret); dprint((7, "- adrbk_check_and_fix_all -\n")); last_check_and_fix_all = get_adj_time(); for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->address_book) ret += adrbk_check_and_fix(pab, safe, low_freq, check_now); } return(ret); } /* * */ void adrbk_maintenance(void) { static time_t last_time_here = 0; time_t now; int i; long low_freq_interval; dprint((9, "- adrbk_maintenance -\n")); if(!as.initialized) return; now = get_adj_time(); if(now < last_time_here + 120) return; last_time_here = now; low_freq_interval = MAX(LOW_FREQ_CHK_INTERVAL, ps_global->remote_abook_validity + 5); /* check the time here to make it cheap */ if(ab_nesting_level == 0 && ps_global->remote_abook_validity > 0 && now > last_check_and_fix_all + low_freq_interval * 60L) (void)adrbk_check_and_fix_all(1, 1, 0); /* close down idle connections */ for(i = 0; i < as.n_addrbk; i++){ PerAddrBook *pab; pab = &as.adrbks[i]; if(pab->address_book && pab->address_book->type == Imap && pab->address_book->rd && rd_stream_exists(pab->address_book->rd)){ dprint((7, "adrbk_maint: %s: idle cntr %ld (%ld)\n", pab->address_book->orig_filename ? pab->address_book->orig_filename : "?", (long)(now - pab->address_book->rd->last_use), (long)pab->address_book->rd->last_use)); if(now > pab->address_book->rd->last_use + IMAP_IDLE_TIMEOUT){ dprint((2, "adrbk_maint %s: closing idle (%ld secs) connection: %s\n", debug_time(0,0,ps_global->signal_in_progress), (long)(now - pab->address_book->rd->last_use), pab->address_book->orig_filename ? pab->address_book->orig_filename : "?")); rd_close_remote(pab->address_book->rd); } else{ /* * If we aren't going to close it, we ping it instead to * make sure it stays open and doesn't timeout on us. * This shouldn't be necessary unless the server has a * really short timeout. If we got killed for some reason * we set imap.stream to NULL. * Instead of just pinging, we may as well check for * updates, too. */ if(ab_nesting_level == 0 && ps_global->remote_abook_validity > 0){ time_t save_last_use; /* * We shouldn't count this as a real last_use. */ save_last_use = pab->address_book->rd->last_use; (void)adrbk_check_and_fix(pab, 1, 0, 1); pab->address_book->rd->last_use = save_last_use; } /* just ping it if not safe to fix it */ else if(!rd_ping_stream(pab->address_book->rd)){ dprint((2, "adrbk_maint: %s: abook stream closed unexpectedly: %s\n", debug_time(0,0,ps_global->signal_in_progress), pab->address_book->orig_filename ? pab->address_book->orig_filename : "?")); } } } } } /* * Parses a string of comma-separated addresses or nicknames into an * array. * * Returns an allocated, null-terminated list, or NULL. */ char ** parse_addrlist(char *addrfield) { #define LISTCHUNK 500 /* Alloc this many addresses for list at a time */ char **al, **ad; char *next_addr, *cur_addr, *p, *q; int slots = LISTCHUNK; if(!addrfield) return((char **)NULL); /* allocate first chunk */ slots = LISTCHUNK; al = (char **)fs_get(sizeof(char *) * (slots+1)); ad = al; p = addrfield; /* skip any leading whitespace */ for(q = p; *q && *q == SPACE; q++) ;/* do nothing */ next_addr = (*q) ? q : NULL; /* Loop adding each address in list to array al */ for(cur_addr = next_addr; cur_addr; cur_addr = next_addr){ next_addr = skip_to_next_addr(cur_addr); q = cur_addr; SKIP_SPACE(q); /* allocate more space */ if((ad-al) >= slots){ slots += LISTCHUNK; fs_resize((void **)&al, sizeof(char *) * (slots+1)); ad = al + slots - LISTCHUNK; } if(*q) *ad++ = cpystr(q); } *ad++ = NULL; /* free up any excess we've allocated */ fs_resize((void **)&al, sizeof(char *) * (ad - al)); return(al); } /* * Args cur -- pointer to the start of the current addr in list. * * Returns a pointer to the start of the next addr or NULL if there are * no more addrs. * * Side effect: current addr has trailing white space removed * and is null terminated. */ char * skip_to_next_addr(char *cur) { register char *p, *q; char *ret_pointer; int in_quotes = 0, in_comment = 0; char prev_char = '\0'; /* * Find delimiting comma or end. * Quoted commas and commented commas don't count. */ for(q = cur; *q; q++){ switch(*q){ case COMMA: if(!in_quotes && !in_comment) goto found_comma; break; case LPAREN: if(!in_quotes && !in_comment) in_comment = 1; break; case RPAREN: if(in_comment && prev_char != BSLASH) in_comment = 0; break; case QUOTE: if(in_quotes && prev_char != BSLASH) in_quotes = 0; else if(!in_quotes && !in_comment) in_quotes = 1; break; default: break; } prev_char = *q; } found_comma: if(*q){ /* trailing comma case */ *q = '\0'; ret_pointer = q + 1; } else ret_pointer = NULL; /* no more addrs after cur */ /* remove trailing white space from cur */ for(p = q - 1; p >= cur && isspace((unsigned char)*p); p--) *p = '\0'; return(ret_pointer); } /* * Add entries specified by system administrator. If the nickname already * exists, it is not touched. */ void add_forced_entries(AdrBk *abook) { AdrBk_Entry *abe; char *nickname, *fullname, *address; char *end_of_nick, *end_of_full, **t; if(!ps_global->VAR_FORCED_ABOOK_ENTRY || !ps_global->VAR_FORCED_ABOOK_ENTRY[0] || !ps_global->VAR_FORCED_ABOOK_ENTRY[0][0]) return; for(t = ps_global->VAR_FORCED_ABOOK_ENTRY; t[0] && t[0][0]; t++){ nickname = *t; /* * syntax for each element is * nick[whitespace]|[whitespace]Fullname[WS]|[WS]Address */ /* find end of nickname */ end_of_nick = nickname; while(*end_of_nick && !isspace((unsigned char)*end_of_nick) && *end_of_nick != '|') end_of_nick++; /* find the pipe character between nickname and fullname */ fullname = end_of_nick; while(*fullname && *fullname != '|') fullname++; if(*fullname) fullname++; *end_of_nick = '\0'; abe = adrbk_lookup_by_nick(abook, nickname, NULL); if(!abe){ /* If it isn't there, add it */ /* skip whitespace before fullname */ fullname = skip_white_space(fullname); /* find the pipe character between fullname and address */ end_of_full = fullname; while(*end_of_full && *end_of_full != '|') end_of_full++; if(!*end_of_full){ dprint((2, "missing | in forced-abook-entry \"%s\"\n", nickname ? nickname : "?")); continue; } address = end_of_full + 1; /* skip whitespace before address */ address = skip_white_space(address); if(*address == '('){ dprint((2, "no lists allowed in forced-abook-entry \"%s\"\n", address ? address : "?")); continue; } /* go back and remove trailing white space from fullname */ while(*end_of_full == '|' || isspace((unsigned char)*end_of_full)){ *end_of_full = '\0'; end_of_full--; } dprint((2, "Adding forced abook entry \"%s\"\n", nickname ? nickname : "")); (void)adrbk_add(abook, NO_NEXT, nickname, fullname, address, NULL, NULL, Single, (adrbk_cntr_t *)NULL, (int *)NULL, 1, 0, 1); } } } /* Go through the list of addressbooks and check if any * of them point to the given stream. */ int any_addressbook_in_remote_stream(MAILSTREAM *stream) { int rv = 0; int i = 0, num = 0; char *nickname = NULL; char *filename = NULL; char *q = NULL; do{ if(ps_global->VAR_ADDRESSBOOK && ps_global->VAR_ADDRESSBOOK[num] && ps_global->VAR_ADDRESSBOOK[num][0]){ q = ps_global->VAR_ADDRESSBOOK[num++]; i = num; } else if(ps_global->VAR_GLOB_ADDRBOOK && ps_global->VAR_GLOB_ADDRBOOK[i-num] && ps_global->VAR_GLOB_ADDRBOOK[i-num][0]){ q = ps_global->VAR_GLOB_ADDRBOOK[i - num]; i++; } else q = NULL; if(q != NULL){ get_pair(q, &nickname, &filename, 0, 0); if(nickname) fs_give((void **)&nickname); if(filename){ if(*filename == '{' && same_stream(filename, stream) != NULL) rv = 1; fs_give((void **)&filename); } } } while (rv == 0 && q != NULL); return rv; }