summaryrefslogtreecommitdiff
path: root/alpine/adrbkcmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'alpine/adrbkcmd.c')
-rw-r--r--alpine/adrbkcmd.c7694
1 files changed, 7694 insertions, 0 deletions
diff --git a/alpine/adrbkcmd.c b/alpine/adrbkcmd.c
new file mode 100644
index 00000000..9320160d
--- /dev/null
+++ b/alpine/adrbkcmd.c
@@ -0,0 +1,7694 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: adrbkcmd.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * 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
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ adrbkcmds.c
+ Commands called from the addrbook screens.
+ ====*/
+
+
+#include "headers.h"
+#include "adrbkcmd.h"
+#include "addrbook.h"
+#include "takeaddr.h"
+#include "status.h"
+#include "keymenu.h"
+#include "mailview.h"
+#include "mailcmd.h"
+#include "mailindx.h"
+#include "radio.h"
+#include "folder.h"
+#include "reply.h"
+#include "help.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "roleconf.h"
+#include "send.h"
+#include "../pith/adrbklib.h"
+#include "../pith/addrbook.h"
+#include "../pith/abdlc.h"
+#include "../pith/ablookup.h"
+#include "../pith/bldaddr.h"
+#include "../pith/ldap.h"
+#include "../pith/state.h"
+#include "../pith/filter.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/addrstring.h"
+#include "../pith/remote.h"
+#include "../pith/url.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/stream.h"
+#include "../pith/send.h"
+#include "../pith/list.h"
+#include "../pith/busy.h"
+#include "../pith/icache.h"
+#include "../pith/osdep/color.h"
+
+
+/* internal prototypes */
+int url_hilite_abook(long, char *, LT_INS_S **, void *);
+int process_abook_view_cmd(int, MSGNO_S *, SCROLL_S *);
+int expand_addrs_for_pico(struct headerentry *, char ***);
+char *view_message_for_pico(char **);
+int verify_nick(char *, char **, char **, BUILDER_ARG *, int *);
+int verify_addr(char *, char **, char **, BUILDER_ARG *, int *);
+int pico_sendexit_for_adrbk(struct headerentry *, void(*)(void), int, char **);
+char *pico_cancelexit_for_adrbk(char *, void (*)(void));
+char *pico_cancel_for_adrbk_take(void (*)(void));
+char *pico_cancel_for_adrbk_edit(void (*)(void));
+int ab_modify_abook_list(int, int, int, char *, char *, char *);
+int convert_abook_to_remote(struct pine *, PerAddrBook *, char *, size_t, int);
+int any_rule_files_to_warn_about(struct pine *);
+int verify_folder_name(char *,char **,char **,BUILDER_ARG *, int *);
+int verify_server_name(char *,char **,char **,BUILDER_ARG *, int *);
+int verify_abook_nick(char *, char **,char **,BUILDER_ARG *, int *);
+int do_the_shuffle(int *, int, int, char **);
+void ab_compose_internal(BuildTo, int);
+int ab_export(struct pine *, long, int, int);
+VCARD_INFO_S *prepare_abe_for_vcard(struct pine *, AdrBk_Entry *, int);
+void write_single_tab_entry(gf_io_t, VCARD_INFO_S *);
+int percent_done_copying(void);
+int cmp_action_list(const qsort_t *, const qsort_t *);
+void set_act_list_member(ACTION_LIST_S *, a_c_arg_t, PerAddrBook *, PerAddrBook *, char *);
+void convert_pinerc_to_remote(struct pine *, char *);
+
+#ifdef ENABLE_LDAP
+typedef struct _saved_query {
+ char *qq,
+ *cn,
+ *sn,
+ *gn,
+ *mail,
+ *org,
+ *unit,
+ *country,
+ *state,
+ *locality,
+ *custom;
+} SAVED_QUERY_S;
+
+int process_ldap_cmd(int, MSGNO_S *, SCROLL_S *);
+int pico_simpleexit(struct headerentry *, void (*)(void), int, char **);
+char *pico_simplecancel(void (*)(void));
+void save_query_parameters(SAVED_QUERY_S *);
+SAVED_QUERY_S *copy_query_parameters(SAVED_QUERY_S *);
+void free_query_parameters(SAVED_QUERY_S **);
+int restore_query_parameters(struct headerentry *, char ***);
+
+static char *expander_address;
+#endif /* ENABLE_LDAP */
+
+static char *fakedomain = "@";
+
+
+#define VIEW_ABOOK_NONE 0
+#define VIEW_ABOOK_EDITED 1
+#define VIEW_ABOOK_WARPED 2
+
+/*
+ * View an addrbook entry.
+ * Call scrolltool to do the work.
+ */
+void
+view_abook_entry(struct pine *ps, long int cur_line)
+{
+ AdrBk_Entry *abe;
+ STORE_S *in_store, *out_store;
+ char *string, *errstr;
+ SCROLL_S sargs;
+ HANDLE_S *handles = NULL;
+ URL_HILITE_S uh;
+ gf_io_t pc, gc;
+ int cmd, abook_indent;
+ long offset = 0L;
+ char b[500];
+
+ dprint((5, "- view_abook_entry -\n"));
+
+ if(is_addr(cur_line)){
+ abe = ae(cur_line);
+ if(!abe){
+ q_status_message(SM_ORDER, 0, 3, _("Error reading entry"));
+ return;
+ }
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3, _("Nothing to view"));
+ return;
+ }
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ return;
+ }
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ /* TRANSLATORS: Nickname is a shorthand name for something */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ so_puts(in_store, b);
+ if(abe->nickname)
+ so_puts(in_store, abe->nickname);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Full name is the name that goes with an email address.
+ For example, in
+ Fred Flintstone <fred@bedrock.org>
+ Fred Flintstone is the Full Name. */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ so_puts(in_store, b);
+ if(abe->fullname)
+ so_puts(in_store, abe->fullname);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Fcc is an abbreviation for File carbon copy. It is like
+ a cc which is a copy of a message that goes to somebody other than the
+ main recipient, only this is a copy of a message which is put into a
+ file on the user's computer. */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ so_puts(in_store, b);
+ if(abe->fcc)
+ so_puts(in_store, abe->fcc);
+
+ so_puts(in_store, "\015\012");
+
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, AB_COMMENT_STR);
+ so_puts(in_store, b);
+ if(abe->extra)
+ so_puts(in_store, abe->extra);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Addresses refers to email Addresses */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ so_puts(in_store, b);
+ if(abe->tag == Single){
+ char *tmp = abe->addr.addr ? abe->addr.addr : "";
+#ifdef ENABLE_LDAP
+ if(!strncmp(tmp, QRUN_LDAP, LEN_QRL))
+ string = LDAP_DISP;
+ else
+#endif
+ string = tmp;
+
+ so_puts(in_store, string);
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(ll != abe->addr.list){
+ so_puts(in_store, "\015\012");
+ so_puts(in_store, repeat_char(abook_indent+2, SPACE));
+ }
+
+#ifdef ENABLE_LDAP
+ if(!strncmp(*ll, QRUN_LDAP, LEN_QRL))
+ string = LDAP_DISP;
+ else
+#endif
+ string = *ll;
+
+ so_puts(in_store, string);
+
+ if(*(ll+1)) /* not the last one */
+ so_puts(in_store, ",");
+ }
+ }
+
+ so_puts(in_store, "\015\012");
+
+ do{
+ if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ so_give(&in_store);
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space."));
+ return;
+ }
+
+ so_seek(in_store, 0L, 0);
+
+ init_handles(&handles);
+ gf_filter_init();
+
+ if(F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite_abook,
+ gf_url_hilite_opt(&uh,&handles,0)));
+
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(ps->ttyo->screen_cols - 4,
+ ps->ttyo->screen_cols,
+ NULL, abook_indent+2,
+ GFW_HANDLES));
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+
+ if((errstr = gf_pipe(gc, pc)) != NULL){
+ so_give(&in_store);
+ so_give(&out_store);
+ free_handles(&handles);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ /* TRANSLATORS: %s is the error message */
+ _("Can't format entry: %s"), errstr);
+ return;
+ }
+
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("expanded entry");
+ sargs.text.handles = handles;
+
+ if(offset){ /* resize? preserve paging! */
+ sargs.start.on = Offset;
+ sargs.start.loc.offset = offset;
+ offset = 0L;
+ }
+
+ /* TRANSLATORS: a screen title. We are viewing an address book */
+ sargs.bar.title = _("ADDRESS BOOK (View)");
+ sargs.bar.style = TextPercent;
+ sargs.proc.tool = process_abook_view_cmd;
+ sargs.proc.data.i = VIEW_ABOOK_NONE;
+ sargs.resize_exit = 1;
+ sargs.help.text = h_abook_view;
+ /* TRANSLATORS: help screen title */
+ sargs.help.title = _("HELP FOR ADDRESS BOOK VIEW");
+ sargs.keys.menu = &abook_view_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ if(handles)
+ sargs.keys.menu->how_many = 2;
+ else{
+ sargs.keys.menu->how_many = 1;
+ clrbitn(OTHER_KEY, sargs.keys.bitmap);
+ }
+
+ if((cmd = scrolltool(&sargs)) == MC_RESIZE)
+ offset = sargs.start.loc.offset;
+
+ so_give(&out_store);
+ free_handles(&handles);
+ }
+ while(cmd == MC_RESIZE);
+
+ so_give(&in_store);
+
+ if(sargs.proc.data.i != VIEW_ABOOK_NONE){
+ long old_l_p_p, old_top_ent, old_cur_row;
+
+ if(sargs.proc.data.i == VIEW_ABOOK_WARPED){
+ /*
+ * Warped means we got plopped down somewhere in the display
+ * list so that we don't know where we are relative to where
+ * we were before we warped. The current line number will
+ * be zero, since that is what the warp would have set.
+ */
+ as.top_ent = first_line(0L - as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+ else if(sargs.proc.data.i == VIEW_ABOOK_EDITED){
+ old_l_p_p = as.l_p_page;
+ old_top_ent = as.top_ent;
+ old_cur_row = as.cur_row;
+ }
+
+ /* Window size may have changed while in pico. */
+ ab_resize();
+
+ /* fix up what ab_resize messed up */
+ if(sargs.proc.data.i != VIEW_ABOOK_WARPED && old_l_p_p == as.l_p_page){
+ as.top_ent = old_top_ent;
+ as.cur_row = old_cur_row;
+ as.old_cur_row = old_cur_row;
+ }
+
+ cur_line = as.top_ent+as.cur_row;
+ }
+
+ ps->mangled_screen = 1;
+}
+
+
+int
+url_hilite_abook(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ register char *lp;
+
+ if((lp = strchr(line, ':')) &&
+ !strncmp(line, AB_COMMENT_STR, strlen(AB_COMMENT_STR)))
+ (void) url_hilite(linenum, lp + 1, ins, local);
+
+ return(0);
+}
+
+
+int
+process_abook_view_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1, i;
+ PerAddrBook *pab;
+ AddrScrn_Disp *dl;
+ static ESCKEY_S text_or_vcard[] = {
+ /* TRANSLATORS: Text refers to plain old text, probably the text of
+ an email message */
+ {'t', 't', "T", N_("Text")},
+ /* TRANSLATORS: A VCard is kind of like an electronic business card. It is not
+ something specific to alpine, it is universal. */
+ {'v', 'v', "V", N_("VCard")},
+ {-1, 0, NULL, NULL}};
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_EDIT :
+ /*
+ * MC_EDIT works a little differently from the other cmds here.
+ * The others return 0 to scrolltool so that we are still in
+ * the view screen. This one is different because we may have
+ * changed what we're viewing. We handle that by returning 1
+ * to scrolltool and setting the sparms opt union's gint
+ * to the value below.
+ *
+ * (Late breaking news. Now we're going to return 1 from all these
+ * commands and then in the caller we're going to bounce back out
+ * to the index view instead of the view of the individual entry.
+ * So there is some dead code around for now.)
+ *
+ * Then, in the view_abook_entry function we check the value
+ * of this field on scrolltool's return and if it is one of
+ * the two special values below view_abook_entry resets the
+ * current line if necessary, flushes the display cache (in
+ * ab_resize) and loops back and starts over, effectively
+ * putting us back in the view screen but with updated
+ * contents. A side effect is that the screen above that (the
+ * abook index) will also have been flushed and corrected by
+ * the ab_resize.
+ */
+ pab = &as.adrbks[cur_addr_book()];
+ if(pab && pab->access == ReadOnly){
+ /* TRANSLATORS: Address book can be viewed but not changed */
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ rv = 0;
+ break;
+ }
+
+ if(adrbk_check_all_validity_now()){
+ if(resync_screen(pab, AddrBookScreen, 0)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: The address book was changed by some other
+ process. The user is being told that their change (their
+ Update) has been canceled and they should try again. */
+ _("Address book changed. Update cancelled. Try again."));
+ ps_global->mangled_screen = 1;
+ break;
+ }
+ }
+
+ if(pab && pab->access == ReadOnly){
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ rv = 0;
+ break;
+ }
+
+ /*
+ * Arguments all come from globals, not from the arguments to
+ * process_abook_view_cmd. It would be a little cleaner if the
+ * information was contained in att, I suppose.
+ */
+ if(is_addr(as.cur_row+as.top_ent)){
+ AdrBk_Entry *abe, *abe_copy;
+ a_c_arg_t entry;
+ int warped = 0;
+
+ dl = dlist(as.top_ent+as.cur_row);
+ entry = dl->elnum;
+ abe = adrbk_get_ae(pab->address_book, entry);
+ abe_copy = copy_ae(abe);
+ dprint((9,"Calling edit_entry to edit from view\n"));
+ /* TRANSLATORS: update as in update an address book entry */
+ edit_entry(pab->address_book, abe_copy, entry,
+ abe->tag, 0, &warped, _("update"));
+ /*
+ * The ABOOK_EDITED case doesn't mean that we necessarily
+ * changed something, just that we might have but we know
+ * we didn't change the sort order (causing the warp).
+ */
+ sparms->proc.data.i = warped
+ ? VIEW_ABOOK_WARPED : VIEW_ABOOK_EDITED;
+
+ free_ae(&abe_copy);
+ rv = 1; /* causes scrolltool to return */
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4,
+ "Something wrong, entry not updateable");
+ rv = 0;
+ break;
+ }
+
+ break;
+
+ case MC_SAVE :
+ pab = &as.adrbks[cur_addr_book()];
+ /*
+ * Arguments all come from globals, not from the arguments to
+ * process_abook_view_cmd. It would be a little cleaner if the
+ * information was contained in att, I suppose.
+ */
+ (void)ab_save(ps_global, pab ? pab->address_book : NULL,
+ as.top_ent+as.cur_row, -FOOTER_ROWS(ps_global), 0);
+ rv = 1;
+ break;
+
+ case MC_COMPOSE :
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
+ rv = 1;
+ break;
+
+ case MC_ROLE :
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
+ rv = 1;
+ break;
+
+ case MC_FORWARD :
+ rv = 1;
+ /* TRANSLATORS: A question with two choices for the answer. Forward
+ as text means to include the text of the message being forwarded
+ in the new message. Forward as a Vcard attachment means to
+ attach it to the message in a special format so it is recognizable
+ as a Vcard. */
+ i = radio_buttons(_("Forward as text or forward as Vcard attachment ? "),
+ -FOOTER_ROWS(ps_global), text_or_vcard, 't', 'x',
+ h_ab_text_or_vcard, RB_NORM);
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
+ rv = 0;
+ break;
+
+ case 't':
+ forward_text(ps_global, sparms->text.text, sparms->text.src);
+ break;
+
+ case 'v':
+ (void)ab_forward(ps_global, as.top_ent+as.cur_row, 0);
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3,
+ "can't happen in process_abook_view_cmd");
+ break;
+ }
+
+ break;
+
+ default:
+ panic("Unexpected command in process_abook_view_cmd");
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Give expanded view of this address entry.
+ * Call scrolltool to do the work.
+ *
+ * Args: headents -- The headerentry array from pico.
+ * s -- Unused here.
+ *
+ * Returns -- Always 0.
+ */
+int
+expand_addrs_for_pico(struct headerentry *headents, char ***s)
+{
+ BuildTo bldto;
+ STORE_S *store;
+ char *error = NULL, *addr = NULL, *fullname = NULL, *address = NULL;
+ SAVE_STATE_S state;
+ ADDRESS *adrlist = NULL, *a;
+ int j, address_index = -1, fullname_index = -1, no_a_fld = 0;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ char *tmp, *tmp2, *tmp3;
+ SCROLL_S sargs;
+ AdrBk_Entry abe;
+ char fakeaddrpmt[500];
+ int abook_indent;
+
+ dprint((5, "- expand_addrs_for_pico -\n"));
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+ utf8_snprintf(fakeaddrpmt, sizeof(fakeaddrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Address"));
+
+ if(s)
+ *s = NULL;
+
+ ps_global->redrawer = NULL;
+ fix_windsize(ps_global);
+
+ ab_nesting_level++;
+ save_state(&state);
+
+ for(j=0;
+ headents[j].name != NULL && (address_index < 0 || fullname_index < 0);
+ j++){
+ if(!strncmp(headents[j].name, "Address", 7) || !strncmp(headents[j].name, _("Address"), strlen(_("Address"))))
+ address_index = j;
+ else if(!strncmp(headents[j].name, "Fullname", 8) || !strncmp(headents[j].name, _("Fullname"), strlen(_("Fullname"))))
+ fullname_index = j;
+ }
+
+ if(address_index >= 0)
+ address = *headents[address_index].realaddr;
+ else{
+ address_index = 1000; /* a big number */
+ no_a_fld++;
+ }
+
+ if(fullname_index >= 0)
+ fullname = adrbk_formatname(*headents[fullname_index].realaddr,
+ NULL, NULL);
+
+ memset(&abe, 0, sizeof(abe));
+ if(fullname)
+ abe.fullname = cpystr(fullname);
+
+ if(address){
+ char *tmp_a_string;
+
+ tmp_a_string = cpystr(address);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(adrlist && adrlist->next){
+ int cnt = 0;
+
+ abe.tag = List;
+ for(a = adrlist; a; a = a->next)
+ cnt++;
+
+ abe.addr.list = (char **)fs_get((cnt+1) * sizeof(char *));
+ cnt = 0;
+ for(a = adrlist; a; a = a->next){
+ if(a->host && a->host[0] == '@')
+ abe.addr.list[cnt++] = cpystr(a->mailbox);
+ else if(a->host && a->host[0] && a->mailbox && a->mailbox[0])
+ abe.addr.list[cnt++] =
+ cpystr(simple_addr_string(a, tmp_20k_buf,
+ SIZEOF_20KBUF));
+ }
+
+ abe.addr.list[cnt] = '\0';
+ }
+ else{
+ abe.tag = Single;
+ abe.addr.addr = address;
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+
+ bldto.type = Abe;
+ bldto.arg.abe = &abe;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ if(addr){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, ps_global->maildomain);
+ fs_give((void **)&tmp_a_string);
+ }
+ }
+#ifdef ENABLE_LDAP
+ else if(no_a_fld && expander_address){
+ WP_ERR_S wp_err;
+
+ memset(&wp_err, 0, sizeof(wp_err));
+ adrlist = wp_lookups(expander_address, &wp_err, 0);
+ if(fullname && *fullname){
+ if(adrlist){
+ if(adrlist->personal)
+ fs_give((void **)&adrlist->personal);
+
+ adrlist->personal = cpystr(fullname);
+ }
+ }
+
+ if(wp_err.error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", wp_err.error);
+ fs_give((void **)&wp_err.error);
+ }
+ }
+#endif
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ restore_state(&state);
+ return(0);
+ }
+
+ for(j = 0; j < address_index && headents[j].name != NULL; j++){
+ if((tmp = fold(*headents[j].realaddr,
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ headents[j].prompt,
+ repeat_char(headents[j].prwid, SPACE), FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+
+ /*
+ * There is an assumption that Addresses is the last field.
+ */
+ tmp3 = cpystr(repeat_char(headents[0].prwid + 2, SPACE));
+ if(adrlist){
+ for(a = adrlist; a; a = a->next){
+ ADDRESS *next_addr;
+ char *bufp;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ (void) addr_string(a, bufp, len);
+ a->next = next_addr;
+
+ /*
+ * Another assumption, all the prwids are the same.
+ */
+ if((tmp = fold(bufp,
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ (a == adrlist) ? (no_a_fld
+ ? fakeaddrpmt
+ : headents[address_index].prompt)
+ : tmp3+2,
+ tmp3, FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **) &tmp);
+ }
+
+ fs_give((void **) &bufp);
+ }
+ }
+ else{
+ tmp2 = NULL;
+ if((tmp = fold(tmp2=cpystr("<none>"),
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ no_a_fld ? fakeaddrpmt
+ : headents[address_index].prompt,
+ tmp3, FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **) &tmp);
+ }
+
+ if(tmp2)
+ fs_give((void **)&tmp2);
+ }
+
+ if(tmp3)
+ fs_give((void **)&tmp3);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("expanded entry");
+ /* TRANSLATORS: screen title for viewing an address book entry in Rich
+ view mode. Rich just means it is expanded to include everything. */
+ sargs.bar.title = _("ADDRESS BOOK (Rich View)");
+ sargs.bar.style = TextPercent;
+ sargs.keys.menu = &abook_text_km;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+ so_give(&store);
+
+ restore_state(&state);
+ ab_nesting_level--;
+
+ if(addr)
+ fs_give((void **)&addr);
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+
+ if(fullname)
+ fs_give((void **)&fullname);
+
+ if(abe.fullname)
+ fs_give((void **)&abe.fullname);
+
+ if(abe.tag == List && abe.addr.list)
+ free_list_array(&(abe.addr.list));
+
+ ps_global->redrawer = redraw;
+
+ return(0);
+}
+
+
+/*
+ * Callback from TakeAddr editing screen to see message that was being
+ * viewed. Call scrolltool to do the work.
+ */
+char *
+view_message_for_pico(char **error)
+{
+ STORE_S *store;
+ gf_io_t pc;
+ void (*redraw)(void) = ps_global->redrawer;
+ SourceType src = CharStar;
+ SCROLL_S sargs;
+
+ dprint((5, "- view_message_for_pico -\n"));
+
+ ps_global->redrawer = NULL;
+ fix_windsize(ps_global);
+
+#ifdef DOS
+ src = TmpFileStar;
+#endif
+
+ if(!(store = so_get(src, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ return(NULL);
+ }
+
+ gf_set_so_writec(&pc, store);
+
+ format_message(msgno_for_pico_callback, env_for_pico_callback,
+ body_for_pico_callback, NULL, FM_NEW_MESS | FM_DISPLAY, pc);
+
+ gf_clear_so_writec(store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = src;
+ sargs.text.desc = _("expanded entry");
+ /* TRANSLATORS: this is a screen title */
+ sargs.bar.title = _("MESSAGE TEXT");
+ sargs.bar.style = TextPercent;
+ sargs.keys.menu = &abook_text_km;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+
+ ps_global->redrawer = redraw;
+
+ return(NULL);
+}
+
+
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::nickcmpl
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_edit[]={
+ {"Nickname : ", N_("Nickname"), h_composer_abook_nick, 12, 0, NULL,
+ /* TRANSLATORS: To AddrBk is a command that takes the user to
+ the address book screen to select an entry from there. */
+ verify_nick, NULL, NULL, addr_book_nick_for_edit, N_("To AddrBk"), NULL, abook_nickname_complete,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Fullname : ", N_("Fullname"), h_composer_abook_full, 12, 0, NULL,
+ NULL, NULL, NULL, view_message_for_pico, N_("To Message"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ /* TRANSLATORS: a File Copy is a copy of a sent message saved in a regular
+ file on the computer's disk */
+ {"Fcc : ", N_("FileCopy"), h_composer_abook_fcc, 12, 0, NULL,
+ /* TRANSLATORS: To Folders */
+ NULL, NULL, NULL, folders_for_fcc, N_("To Fldrs"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Comment : ", N_("Comment"), h_composer_abook_comment, 12, 0, NULL,
+ NULL, NULL, NULL, view_message_for_pico, N_("To Message"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Addresses : ", N_("Addresses"), h_composer_abook_addrs, 12, 0, NULL,
+ verify_addr, NULL, NULL, addr_book_change_list, N_("To AddrBk"), NULL, abook_nickname_complete,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define NNN_NICK 0
+#define NNN_FULL 1
+#define NNN_FCC 2
+#define NNN_COMMENT 3
+#define NNN_ADDR 4
+#define NNN_END 5
+
+static char *nick_saved_for_pico_check;
+static AdrBk *abook_saved_for_pico_check;
+
+/*
+ * Args: abook -- Address book handle
+ * abe -- AdrBk_Entry of old entry to work on. If NULL, this will
+ * be a new entry. This has to be a pointer to a copy of
+ * an abe that won't go away until we finish this function.
+ * In other words, don't pass in a pointer to an abe in
+ * the cache, copy it first. The tag on this abe is only
+ * used to decide whether to read abe->addr.list or
+ * abe->addr.addr, not to determine what the final result
+ * will be. That's determined solely by how many addresses
+ * there are after the user edits.
+ * entry -- The entry number of the old entry that we will be changing.
+ * old_tag -- If we're changing an old entry, then this is the tag of
+ * that old entry.
+ * readonly -- Call pico with readonly flag
+ * warped -- We warped to a new part of the addrbook
+ * (We also overload warped in a couple places and use it's
+ * being set as an indicator of whether we are Taking or
+ * not. It will be NULL if we are Taking.)
+ */
+void
+edit_entry(AdrBk *abook, AdrBk_Entry *abe, a_c_arg_t entry, Tag old_tag, int readonly, int *warped, char *cmd)
+{
+ AdrBk_Entry local_abe;
+ struct headerentry *he;
+ PICO pbf;
+ STORE_S *msgso;
+ adrbk_cntr_t old_entry_num, new_entry_num = NO_NEXT;
+ int rc = 0, resort_happened = 0, list_changed = 0, which_addrbook;
+ int editor_result, i = 0, add, n_end, ldap = 0;
+ char *nick, *full, *fcc, *comment, *fname, *pp;
+ char **orig_addrarray = NULL;
+ char **new_addrarray = NULL;
+ char *comma_sep_addr = NULL;
+ char **p, **q;
+ Tag new_tag;
+ char titlebar[40];
+ char nickpmt[100], fullpmt[100], fccpmt[100], cmtpmt[100], addrpmt[100];
+ int abook_indent;
+ long length;
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+
+ dprint((2, "- edit_entry -\n"));
+
+ old_entry_num = (adrbk_cntr_t) entry;
+ save_state(&state);
+ abook_saved_for_pico_check = abook;
+
+ add = (abe == NULL); /* doing add or change? */
+ if(add){
+ local_abe.nickname = "";
+ local_abe.fullname = "";
+ local_abe.fcc = "";
+ local_abe.extra = "";
+ local_abe.addr.addr = "";
+ local_abe.tag = NotSet;
+ abe = &local_abe;
+ old_entry_num = NO_NEXT;
+ }
+
+ new_tag = abe->tag;
+
+#ifdef ENABLE_LDAP
+ expander_address = NULL;
+ if(abe->tag == Single &&
+ abe->addr.addr &&
+ !strncmp(abe->addr.addr,QRUN_LDAP,LEN_QRL)){
+ ldap = 1;
+ expander_address = cpystr(abe->addr.addr);
+ removing_double_quotes(expander_address);
+ }
+ else if(abe->tag == List &&
+ abe->addr.list &&
+ abe->addr.list[0] &&
+ !abe->addr.list[1] &&
+ !strncmp(abe->addr.list[0],QRUN_LDAP,LEN_QRL)){
+ ldap = 2;
+ expander_address = cpystr(abe->addr.list[0]);
+ removing_double_quotes(expander_address);
+ }
+#endif
+
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_sendexit_for_adrbk;
+ pbf.canceltest = warped ? pico_cancel_for_adrbk_edit
+ : pico_cancel_for_adrbk_take;
+ pbf.expander = expand_addrs_for_pico;
+ pbf.ctrlr_label = _("RichView");
+ /* xgettext: c-format */
+ if(readonly)
+ /* TRANSLATORS: screen titles */
+ snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (View)"));
+ else
+ snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (%c%s)"),
+ islower((unsigned char)(*cmd))
+ ? toupper((unsigned char)*cmd)
+ : *cmd, (cmd+1));
+
+ pbf.pine_anchor = set_titlebar(titlebar,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+ if(readonly)
+ pbf.pine_flags |= P_VIEW;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them. So just make each line a whole sentence
+ * that doesn't need the others below it to make sense.
+ */
+ if(add){
+ so_puts(msgso,
+/*
+ * TRANSLATORS: The following lines go together to form a screen of
+ * explanation about how to edit an address book entry.
+ */
+_("\n Fill in the fields. It is ok to leave fields blank."));
+ so_puts(msgso,
+_("\n To form a list, just enter multiple comma-separated addresses."));
+ }
+ else{
+ so_puts(msgso,
+/* TRANSLATORS: Same here, but a different version of the screen. */
+_("\n Change any of the fields. It is ok to leave fields blank."));
+ if(ldap)
+ so_puts(msgso,
+_("\n Since this entry does a directory lookup you may not edit the address field."));
+ else
+ so_puts(msgso,
+_("\n Additional comma-separated addresses may be entered in the address field."));
+ }
+
+ so_puts(msgso,
+_("\n Press \"^X\" to save the entry, \"^C\" to cancel, \"^G\" for help."));
+ so_puts(msgso,
+_("\n If you want to use quotation marks inside the Fullname field, it is best"));
+ so_puts(msgso,
+_("\n to use single quotation marks; for example: George 'Husky' Washington."));
+ }
+
+ he = (struct headerentry *) fs_get((NNN_END+1) * sizeof(struct headerentry));
+ memset((void *)he, 0, (NNN_END+1) * sizeof(struct headerentry));
+ pbf.headents = he;
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ /* make a copy of each field */
+ nick = cpystr(abe->nickname ? abe->nickname : "");
+ removing_leading_and_trailing_white_space(nick);
+ nick_saved_for_pico_check = cpystr(nick);
+ he[NNN_NICK] = headents_for_edit[NNN_NICK];
+ he[NNN_NICK].realaddr = &nick;
+ utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ he[NNN_NICK].prompt = nickpmt;
+ he[NNN_NICK].prwid = abook_indent+2;
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he[NNN_NICK].nickcmpl = NULL;
+
+ full = cpystr(abe->fullname ? abe->fullname : "");
+ removing_leading_and_trailing_white_space(full);
+ he[NNN_FULL] = headents_for_edit[NNN_FULL];
+ he[NNN_FULL].realaddr = &full;
+ utf8_snprintf(fullpmt, sizeof(fullpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ he[NNN_FULL].prompt = fullpmt;
+ he[NNN_FULL].prwid = abook_indent+2;
+
+ fcc = cpystr(abe->fcc ? abe->fcc : "");
+ removing_leading_and_trailing_white_space(fcc);
+ he[NNN_FCC] = headents_for_edit[NNN_FCC];
+ he[NNN_FCC].realaddr = &fcc;
+ utf8_snprintf(fccpmt, sizeof(fccpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ he[NNN_FCC].prompt = fccpmt;
+ he[NNN_FCC].prwid = abook_indent+2;
+
+ comment = cpystr(abe->extra ? abe->extra : "");
+ removing_leading_and_trailing_white_space(comment);
+ he[NNN_COMMENT] = headents_for_edit[NNN_COMMENT];
+ he[NNN_COMMENT].realaddr = &comment;
+ utf8_snprintf(cmtpmt, sizeof(cmtpmt), "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
+ he[NNN_COMMENT].prompt = cmtpmt;
+ he[NNN_COMMENT].prwid = abook_indent+2;
+
+ n_end = NNN_END;
+ if(ldap)
+ n_end--;
+
+ if(!ldap){
+ he[NNN_ADDR] = headents_for_edit[NNN_ADDR];
+ he[NNN_ADDR].realaddr = &comma_sep_addr;
+ utf8_snprintf(addrpmt, sizeof(addrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ he[NNN_ADDR].prompt = addrpmt;
+ he[NNN_ADDR].prwid = abook_indent+2;
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he[NNN_NICK].nickcmpl = NULL;
+
+ if(abe->tag == Single){
+ if(abe->addr.addr){
+ orig_addrarray = (char **) fs_get(2 * sizeof(char *));
+ orig_addrarray[0] = cpystr(abe->addr.addr);
+ orig_addrarray[1] = NULL;
+ }
+ }
+ else if(abe->tag == List){
+ if(listmem_count_from_abe(abe) > 0){
+ orig_addrarray = (char **) fs_get(
+ (size_t)(listmem_count_from_abe(abe) + 1)
+ * sizeof(char *));
+ for(q = orig_addrarray, p = abe->addr.list; p && *p; p++, q++)
+ *q = cpystr(*p);
+
+ *q = NULL;
+ }
+ }
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(p = orig_addrarray; p && *p; p++)
+ length += (strlen(*p) + 2);
+
+ if(length)
+ length -= 2L;
+
+ pp = comma_sep_addr = (char *) fs_get((size_t)(length+1L) * sizeof(char));
+ *pp = '\0';
+ for(p = orig_addrarray; p && *p; p++){
+ sstrncpy(&pp, *p, length-(pp-comma_sep_addr));
+ if(*(p+1))
+ sstrncpy(&pp, ", ", length-(pp-comma_sep_addr));
+ }
+
+ comma_sep_addr[length] = '\0';
+
+ if(verify_addr(comma_sep_addr, NULL, NULL, NULL, NULL) < 0)
+ he[NNN_ADDR].start_here = 1;
+ }
+
+ he[n_end] = headents_for_edit[NNN_END];
+ for(i = 0; i < n_end; i++){
+ /* no callbacks in some cases */
+ if(readonly || ((i == NNN_FULL || i == NNN_COMMENT) && !env_for_pico_callback)){
+ he[i].selector = NULL;
+ he[i].key_label = NULL;
+ }
+
+ /* no builders for readonly */
+ if(readonly)
+ he[i].builder = NULL;
+ }
+
+ /* pass to pico and let user change them */
+ editor_result = pico(&pbf);
+ ps_global->mangled_screen = 1;
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP)
+ hup_signal();
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & COMP_CANCEL){
+ if(!readonly)
+ /* TRANSLATOR: Something like
+ Address book save cancelled */
+ q_status_message1(SM_INFO, 0, 2, _("Address book %s cancelled"), cmd);
+ }
+ else if(editor_result & COMP_EXIT){
+ if(pico_usingcolor())
+ clear_index_cache(ps_global->mail_stream, 0);
+ removing_leading_and_trailing_white_space(nick);
+ removing_leading_and_trailing_white_space(full);
+ removing_leading_and_trailing_white_space(fcc);
+ removing_leading_and_trailing_white_space(comment);
+ removing_leading_and_trailing_white_space(comma_sep_addr);
+
+ /* not needed if pico is returning UTF-8 */
+ convert_possibly_encoded_str_to_utf8(&nick);
+ convert_possibly_encoded_str_to_utf8(&full);
+ convert_possibly_encoded_str_to_utf8(&fcc);
+ convert_possibly_encoded_str_to_utf8(&comment);
+ convert_possibly_encoded_str_to_utf8(&comma_sep_addr);
+
+ /* don't allow adding null entry */
+ if(add && !*nick && !*full && !*fcc && !*comment && !*comma_sep_addr)
+ goto outtahere;
+
+ /*
+ * comma_sep_addr is now the string which has been edited
+ */
+ if(comma_sep_addr)
+ new_addrarray = parse_addrlist(comma_sep_addr);
+
+ if(!ldap && (!new_addrarray || !new_addrarray[0]))
+ q_status_message(SM_ORDER, 3, 5, _("Warning: entry has no addresses"));
+
+ if(!new_addrarray || !new_addrarray[0] || !new_addrarray[1])
+ new_tag = Single; /* one or zero addresses means its a Single */
+ else
+ new_tag = List; /* more than one addresses means its a List */
+
+ if(new_tag == List && old_tag == List){
+ /*
+ * If Taking, make sure we write it even if user didn't edit
+ * it any further.
+ */
+ if(!warped)
+ list_changed++;
+ else if(he[NNN_ADDR].dirty)
+ for(q = orig_addrarray, p = new_addrarray; p && *p && q && *q; p++, q++)
+ if(strcmp(*p, *q) != 0){
+ list_changed++;
+ break;
+ }
+
+ if(!list_changed && he[NNN_ADDR].dirty
+ && ((!(p && *p) && (q && *q)) || ((p && *p) && !(q && *q))))
+ list_changed++;
+
+ if(list_changed){
+ /*
+ * need to delete old list members and add new members below
+ */
+ rc = adrbk_listdel_all(abook, (a_c_arg_t) old_entry_num);
+ }
+ else{
+ /* don't need new_addrarray */
+ free_list_array(&new_addrarray);
+ }
+
+ if(comma_sep_addr)
+ fs_give((void **) &comma_sep_addr);
+ }
+ else if((new_tag == List && old_tag == Single)
+ || (new_tag == Single && old_tag == List)){
+ /* delete old entry */
+ rc = adrbk_delete(abook, (a_c_arg_t) old_entry_num, 0, 0, 0, 0);
+ old_entry_num = NO_NEXT;
+ if(comma_sep_addr && new_tag == List)
+ fs_give((void **) &comma_sep_addr);
+ }
+
+ /*
+ * This will be an edit in the cases where the tag didn't change
+ * and an add in the cases where it did.
+ */
+ if(rc == 0)
+ rc = adrbk_add(abook,
+ (a_c_arg_t)old_entry_num,
+ nick,
+ he[NNN_FULL].dirty ? full : abe->fullname,
+ new_tag == Single ? (ldap == 1 ? abe->addr.addr :
+ ldap == 2 ? abe->addr.list[0] :
+ comma_sep_addr)
+ : NULL,
+ fcc,
+ he[NNN_COMMENT].dirty ? comment : abe->extra,
+ new_tag,
+ &new_entry_num,
+ &resort_happened,
+ 1,
+ 0,
+ (new_tag != List || !new_addrarray));
+ }
+
+ if(rc == 0 && new_tag == List && new_addrarray)
+ rc = adrbk_nlistadd(abook, (a_c_arg_t) new_entry_num, &new_entry_num,
+ &resort_happened, new_addrarray, 1, 0, 1);
+
+ restore_state(&state);
+
+ if(rc == -2 || rc == -3){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Error updating address book: %s"),
+ rc == -2 ? error_description(errno) : "Alpine bug");
+ }
+ else if(rc == 0
+ && strucmp(nick, nick_saved_for_pico_check) != 0
+ && (editor_result & COMP_EXIT)){
+ int added_to;
+
+ for(added_to = 0; added_to < as.n_addrbk; added_to++)
+ if(abook_saved_for_pico_check == as.adrbks[added_to].address_book)
+ break;
+
+ if(added_to >= as.n_addrbk)
+ added_to = -1;
+
+ fname = addr_lookup(nick, &which_addrbook, added_to);
+ if(fname){
+ q_status_message4(SM_ORDER, 5, 9,
+ /* TRANSLATORS: The first %s is the nickname the user is
+ trying to use that exists in another address book.
+ The second %s is the name of the other address book.
+ The third %s is " as " because it will say something
+ like also exists in <name> as <description>. */
+ _("Warning! Nickname %s also exists in \"%s\"%s%s"),
+ nick, as.adrbks[which_addrbook].abnick,
+ (fname && *fname) ? _(" as ") : "",
+ (fname && *fname) ? fname : "");
+ fs_give((void **)&fname);
+ }
+ }
+
+ if(resort_happened || list_changed){
+ DL_CACHE_S dlc_restart;
+
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.dlcelnum = new_entry_num;
+ switch(new_tag){
+ case Single:
+ dlc_restart.type = DlcSimple;
+ break;
+
+ case List:
+ dlc_restart.type = DlcListHead;
+ break;
+
+ default:
+ break;
+ }
+
+ warp_to_dlc(&dlc_restart, 0L);
+ if(warped)
+ *warped = 1;
+ }
+
+outtahere:
+ if(he)
+ free_headents(&he);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(nick)
+ fs_give((void **)&nick);
+ if(full)
+ fs_give((void **)&full);
+ if(fcc)
+ fs_give((void **)&fcc);
+ if(comment)
+ fs_give((void **)&comment);
+
+ if(comma_sep_addr)
+ fs_give((void **)&comma_sep_addr);
+ if(nick_saved_for_pico_check)
+ fs_give((void **)&nick_saved_for_pico_check);
+
+ free_list_array(&orig_addrarray);
+ free_list_array(&new_addrarray);
+#ifdef ENABLE_LDAP
+ if(expander_address)
+ fs_give((void **) &expander_address);
+#endif
+}
+
+
+/*ARGSUSED*/
+int
+verify_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ dprint((7, "- verify_nick - (%s)\n", given ? given : "nul"));
+
+ tmp = cpystr(given);
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(nickname_check(tmp, error)){
+ fs_give((void **)&tmp);
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return -2;
+ }
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ ab_nesting_level++;
+ if(strucmp(tmp, nick_saved_for_pico_check) != 0
+ && adrbk_lookup_by_nick(abook_saved_for_pico_check,
+ tmp, (adrbk_cntr_t *)NULL)){
+ if(error){
+ char buf[MAX_NICKNAME + 80];
+
+ /* TRANSLATORS: The %s is the nickname of an entry that is already
+ in the address book */
+ snprintf(buf, sizeof(buf), _("\"%s\" already in address book."), tmp);
+ buf[sizeof(buf)-1] = '\0';
+ *error = cpystr(buf);
+ }
+
+ ab_nesting_level--;
+ fs_give((void **)&tmp);
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return -2;
+ }
+
+ ab_nesting_level--;
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ /* This is so pico will erase any old message */
+ if(error)
+ *error = cpystr("");
+
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Args: to -- the passed in line to parse
+ * full_to -- Address of a pointer to return the full address in.
+ * This will be allocated here and freed by the caller.
+ * However, this function is just going to copy "to".
+ * (special case for the route-addr-hack in build_address_int)
+ * We're just looking for the error messages.
+ * error -- Address of a pointer to return an error message in.
+ * This will be allocated here and freed by the caller.
+ * fcc -- This should be passed in NULL.
+ * This builder doesn't support affected_entry's.
+ *
+ * Result: 0 is returned if address was OK,
+ * -2 if address wasn't OK.
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+verify_addr(char *to, char **full_to, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ register char *p;
+ int ret_val;
+ BuildTo bldto;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((7, "- verify_addr - (%s)\n", to ? to : "nul"));
+
+ /* check to see if to string is empty to avoid work */
+ for(p = to; p && *p && isspace((unsigned char)(*p)); p++)
+ ;/* do nothing */
+
+ if(!p || !*p){
+ if(full_to)
+ *full_to = cpystr(to ? to : ""); /* because pico does a strcmp() */
+
+ return 0;
+ }
+
+ if(full_to != NULL)
+ *full_to = (char *)NULL;
+
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ /*
+ * If we end up jumping back here because somebody else changed one of
+ * our addrbooks out from underneath us, we may well leak some memory.
+ * That's probably ok since this will be very rare.
+ */
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ save_nesting_level = cpyint(ab_nesting_level);
+ if(setjmp(addrbook_changed_unexpectedly)){
+ if(full_to && *full_to)
+ fs_give((void **)full_to);
+
+ /* TRANSLATORS: This is sort of an error, something unexpected has
+ happened and alpine is re-initializing the address book. */
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... verify_addr(%s)!\n", to ? to : "?"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = to;
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ ab_nesting_level++;
+
+ ret_val = build_address_internal(bldto, full_to, error, NULL, NULL, NULL,
+ save_and_restore, 1, mangled);
+
+ ab_nesting_level--;
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ if(full_to && *full_to && ret_val >= 0)
+ removing_leading_and_trailing_white_space(*full_to);
+
+ /* This is so pico will erase the old message */
+ if(error != NULL && *error == NULL)
+ *error = cpystr("");
+
+ if(ret_val < 0)
+ ret_val = -2; /* cause pico to stay on same header line */
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ return(ret_val);
+}
+
+
+/*
+ * Call back for pico to prompt the user for exit confirmation
+ *
+ * Returns: either NULL if the user accepts exit, or string containing
+ * reason why the user declined.
+ */
+int
+pico_sendexit_for_adrbk(struct headerentry *he, void (*redraw_pico)(void),
+ int allow_flowed, char **result)
+{
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ /* TRANSLATORS: A question */
+ switch(want_to(_("Exit and save changes "), 'y', 0, NO_HELP, WT_NORM)){
+ case 'y':
+ break;
+
+ case 'n':
+ rstr = _("Use ^C to abandon changes you've made");
+ break;
+ }
+
+ if(result)
+ *result = rstr;
+
+ ps_global->redrawer = redraw;
+ return((rstr == NULL) ? 0 : 1);
+}
+
+
+/*
+ * Call back for pico to prompt the user for exit confirmation
+ *
+ * Returns: either NULL if the user accepts exit, or string containing
+ * reason why the user declined.
+ */
+char *
+pico_cancelexit_for_adrbk(char *word, void (*redraw_pico)(void))
+{
+ char prompt[90];
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ /* TRANSLATORS: A question. The %s is a noun describing what is being cancelled. */
+ snprintf(prompt, sizeof(prompt), _("Cancel %s (answering \"Yes\" will abandon any changes made) "), word);
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ switch(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)){
+ case 'y':
+ rstr = "";
+ break;
+
+ case 'n':
+ case 'x':
+ break;
+ }
+
+ ps_global->redrawer = redraw;
+ return(rstr);
+}
+
+
+char *
+pico_cancel_for_adrbk_take(void (*redraw_pico)(void))
+{
+ return(pico_cancelexit_for_adrbk(_("take"), redraw_pico));
+}
+
+
+char *
+pico_cancel_for_adrbk_edit(void (*redraw_pico)(void))
+{
+ return(pico_cancelexit_for_adrbk(_("changes"), redraw_pico));
+}
+
+
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_add[]={
+ {"Server Name : ", N_("Server"), h_composer_abook_add_server, 14, 0, NULL,
+ verify_server_name, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Folder Name : ", N_("Folder"), h_composer_abook_add_folder, 14, 0, NULL,
+ verify_folder_name, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"NickName : ", N_("Nickname"), h_composer_abook_add_nick, 14, 0, NULL,
+ verify_abook_nick, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define NN_SERVER 0
+#define NN_FOLDER 1
+#define NN_NICK 2
+#define NN_END 3
+
+/*
+ * Args: global -- Add a global address book, not personal.
+ * add_after_this -- This is the addrbook number which should come
+ * right before the new addrbook we're adding, if
+ * that makes sense. If this is -1, append to end
+ * of the list.
+ *
+ * Returns: addrbook number of new addrbook, or
+ * -1, no addrbook added
+ */
+int
+ab_add_abook(int global, int add_after_this)
+{
+ int ret;
+
+ dprint((2, "- ab_add_abook -\n"));
+
+ ret = ab_modify_abook_list(0, global, add_after_this, NULL, NULL, NULL);
+
+ if(ret >= 0)
+ q_status_message(SM_ORDER, 0, 3,
+ _("New address book added. Use \"$\" to adjust order"));
+
+ return(ret);
+}
+
+
+/*
+ * Args: global -- Add a global address book, not personal.
+ * abook_num -- Abook num of the entry we are editing.
+ * serv -- Default server.
+ * folder -- Default folder.
+ * nick -- Default nickname.
+ *
+ * Returns: abook_num if successful,
+ * -1, if not
+ */
+int
+ab_edit_abook(int global, int abook_num, char *serv, char *folder, char *nick)
+{
+ dprint((2, "- ab_edit_abook -\n"));
+
+ return(ab_modify_abook_list(1, global, abook_num, serv, folder, nick));
+}
+
+static int the_one_were_editing;
+
+
+/*
+ * Args: edit -- Edit existing entry
+ * global -- Add a global address book, not personal.
+ * abook_num -- This is the addrbook number which should come
+ * right before the new addrbook we're adding, if
+ * that makes sense. If this is -1, append to end
+ * of the list.
+ * If we are editing instead of adding, this is
+ * the abook number of the entry we are editing.
+ * def_serv -- Default server.
+ * def_fold -- Default folder.
+ * def_nick -- Default nickname.
+ *
+ * Returns: addrbook number of new addrbook, or
+ * -1, no addrbook added
+ */
+int
+ab_modify_abook_list(int edit, int global, int abook_num, char *def_serv, char *def_fold, char *def_nick)
+{
+ struct headerentry *he;
+ PICO pbf;
+ STORE_S *msgso;
+ int editor_result, i, how_many_in_list, new_abook_num, num_in_list;
+ int ret = 0;
+ char *server, *folder, *nickname;
+ char *new_item = NULL;
+ EditWhich ew;
+ AccessType remember_access_result;
+ PerAddrBook *pab;
+ char titlebar[100];
+ char **list, **new_list = NULL;
+ char tmp[1000+MAXFOLDER];
+ int abook_indent;
+ char servpmt[100], foldpmt[100], nickpmt[100];
+ struct variable *vars = ps_global->vars;
+
+ dprint((2, "- ab_modify_abook_list -\n"));
+
+ if(ps_global->readonly_pinerc){
+ if(edit)
+ /* TRANSLATORS: Change was the name of the command the user was
+ trying to perform. It is what was cancelled. */
+ q_status_message(SM_ORDER, 0, 3, _("Change cancelled: config file not changeable"));
+ else
+ /* TRANSLATORS: Add was the command that is being cancelled. */
+ q_status_message(SM_ORDER, 0, 3, _("Add cancelled: config file not changeable"));
+
+ return -1;
+ }
+
+ ew = Main;
+
+ if((global && vars[V_GLOB_ADDRBOOK].is_fixed) ||
+ (!global && vars[V_ADDRESSBOOK].is_fixed)){
+ if(global)
+ /* TRANSLATORS: Operation was cancelled because the system management
+ does not allow the changing of global address books */
+ q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing global address books"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing address books"));
+
+ return -1;
+ }
+
+ init_ab_if_needed();
+
+ if(edit){
+ if((!global &&
+ (abook_num < 0 || abook_num >= as.how_many_personals)) ||
+ (global &&
+ (abook_num < as.how_many_personals ||
+ abook_num >= as.n_addrbk))){
+ dprint((1, "Programming botch in ab_modify_abook_list: global=%d abook_num=%d n_addrbk=%d\n", global, abook_num, as.n_addrbk));
+ q_status_message(SM_ORDER, 0, 3, "Programming botch, bad abook_num");
+ return -1;
+ }
+
+ the_one_were_editing = abook_num;
+ }
+ else
+ the_one_were_editing = -1;
+
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_sendexit_for_adrbk;
+ pbf.canceltest = pico_cancel_for_adrbk_edit;
+ if(edit)
+ /* TRANSLATORS: screen title */
+ strncpy(titlebar, _("CHANGE ADDRESS BOOK"), sizeof(titlebar));
+ else
+ /* TRANSLATORS: screen title */
+ strncpy(titlebar, _("ADD ADDRESS BOOK"), sizeof(titlebar));
+
+ titlebar[sizeof(titlebar)-1] = '\0';
+ pbf.pine_anchor = set_titlebar(titlebar,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int lines_avail;
+char *t1 =
+/* TRANSLATORS: The next few lines go together to explain how to add
+ the address book entry the user is working on. */
+_(" To add a local address book that will be accessed *only* by Alpine running\n on this machine, leave the server field blank.");
+char *t2 =
+_(" To add an address book that will be accessed by IMAP, fill in the\n server name.");
+char *t3 =
+_(" (NOTE: An address book cannot be accessed by IMAP from\n one Alpine and as a local file from another. It is either always accessed\n by IMAP or it is a local address book which is never accessed by IMAP.)");
+char *t4 =
+_(" In the Folder field, type the remote folder name or local file name.");
+char *t5 =
+_(" In the Nickname field, give the address book a nickname or leave it blank.");
+char *t6 =
+_(" To get help specific to an item, press ^G.");
+char *t7 =
+_(" To exit and save the configuration, press ^X. To cancel, press ^C.");
+
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them.
+ *
+ * The 3 is the number of fields to be defined, the 1 is for a
+ * single blank line after the field definitions.
+ */
+ lines_avail = ps_global->ttyo->screen_rows - HEADER_ROWS(ps_global) -
+ FOOTER_ROWS(ps_global) - 3 - 1;
+
+ if(lines_avail >= 15){ /* extra blank line */
+ so_puts(msgso, "\n");
+ lines_avail--;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, t1);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 5){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t2);
+ so_puts(msgso, t3);
+ lines_avail -= 5;
+ }
+ else if(lines_avail >= 3){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t2);
+ lines_avail -= 3;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t4);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t5);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 3){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t6);
+ so_puts(msgso, "\n");
+ so_puts(msgso, t7);
+ }
+ else if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t7);
+ }
+ }
+
+ he = (struct headerentry *)fs_get((NN_END+1) * sizeof(struct headerentry));
+ memset((void *)he, 0, (NN_END+1) * sizeof(struct headerentry));
+ pbf.headents = he;
+
+ abook_indent = utf8_width(_("Server Name")) + 2;
+
+ /* make a copy of each field */
+ server = cpystr(def_serv ? def_serv : "");
+ he[NN_SERVER] = headents_for_add[NN_SERVER];
+ he[NN_SERVER].realaddr = &server;
+ utf8_snprintf(servpmt, sizeof(servpmt), "%-*.*w: ", abook_indent, abook_indent, _("Server Name"));
+ he[NN_SERVER].prompt = servpmt;
+ he[NN_SERVER].prwid = abook_indent+2;
+
+ folder = cpystr(def_fold ? def_fold : "");
+ he[NN_FOLDER] = headents_for_add[NN_FOLDER];
+ he[NN_FOLDER].realaddr = &folder;
+ utf8_snprintf(foldpmt, sizeof(foldpmt), "%-*.*w: ", abook_indent, abook_indent, _("Folder Name"));
+ he[NN_FOLDER].prompt = foldpmt;
+ he[NN_FOLDER].prwid = abook_indent+2;
+
+ nickname = cpystr(def_nick ? def_nick : "");
+ he[NN_NICK] = headents_for_add[NN_NICK];
+ he[NN_NICK].realaddr = &nickname;
+ utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ he[NN_NICK].prompt = nickpmt;
+ he[NN_NICK].prwid = abook_indent+2;
+
+ he[NN_END] = headents_for_add[NN_END];
+
+ /* pass to pico and let user change them */
+ editor_result = pico(&pbf);
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP){
+ ret = -1;
+ hup_signal();
+ }
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & COMP_CANCEL){
+ ret = -1;
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));
+ }
+ else if(editor_result & COMP_EXIT){
+ if(edit &&
+ !strcmp(server, def_serv ? def_serv : "") &&
+ !strcmp(folder, def_fold ? def_fold : "") &&
+ !strcmp(nickname, def_nick ? def_nick : "")){
+ ret = -1;
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("No change: Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("No change: Address book add is cancelled"));
+ }
+ else{
+ if(global){
+ list = VAR_GLOB_ADDRBOOK;
+ how_many_in_list = as.n_addrbk - as.how_many_personals;
+ if(edit)
+ new_abook_num = abook_num;
+ else if(abook_num < 0)
+ new_abook_num = as.n_addrbk;
+ else
+ new_abook_num = MAX(MIN(abook_num + 1, as.n_addrbk),
+ as.how_many_personals);
+
+ num_in_list = new_abook_num - as.how_many_personals;
+ }
+ else{
+ list = VAR_ADDRESSBOOK;
+ how_many_in_list = as.how_many_personals;
+ new_abook_num = abook_num;
+ if(edit)
+ new_abook_num = abook_num;
+ else if(abook_num < 0)
+ new_abook_num = as.how_many_personals;
+ else
+ new_abook_num = MIN(abook_num + 1, as.how_many_personals);
+
+ num_in_list = new_abook_num;
+ }
+
+ if(!edit)
+ how_many_in_list++; /* for new abook */
+
+ removing_leading_and_trailing_white_space(server);
+ removing_leading_and_trailing_white_space(folder);
+ removing_leading_and_trailing_white_space(nickname);
+
+ /* convert nickname to UTF-8 */
+ if(nickname){
+ char *conv;
+
+ conv = convert_to_utf8(nickname, NULL, 0);
+ if(conv){
+ fs_give((void **) &nickname);
+ nickname = conv;
+ }
+ }
+
+ /* eliminate surrounding brackets */
+ if(server[0] == '{' && server[strlen(server)-1] == '}'){
+ char *p;
+
+ server[strlen(server)-1] = '\0';
+ for(p = server; *p; p++)
+ *p = *(p+1);
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s%s%s%.*s",
+ *server ? "{" : "",
+ *server ? server : "",
+ *server ? "}" : "",
+ MAXFOLDER, folder);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ new_item = put_pair(nickname, tmp);
+
+ if(!new_item || *new_item == '\0'){
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));
+
+ ret = -1;
+ goto get_out;
+ }
+
+ /* allocate for new list */
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+
+ /* copy old list up to where we will insert new entry */
+ for(i = 0; i < num_in_list; i++)
+ new_list[i] = cpystr(list[i]);
+
+ /* insert the new entry */
+ new_list[i++] = cpystr(new_item);
+
+ /* copy rest of old list, skip current if editing */
+ for(; i < how_many_in_list; i++)
+ new_list[i] = cpystr(list[edit ? i : (i-1)]);
+
+ new_list[i] = NULL;
+
+ /* this frees old variable contents for us */
+ if(set_variable_list(global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK,
+ new_list, TRUE, ew)){
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Change cancelled: couldn't save configuration file"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Add cancelled: couldn't save configuration file"));
+
+ set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
+ TRUE, FALSE);
+ ret = -1;
+ goto get_out;
+ }
+
+ ret = new_abook_num;
+ set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
+ TRUE, FALSE);
+
+ addrbook_reset();
+ init_ab_if_needed();
+
+ /*
+ * Test to see if this definition is going to work.
+ * Error messages are a good side effect.
+ */
+ pab = &as.adrbks[num_in_list];
+ init_abook(pab, NoDisplay);
+ remember_access_result = pab->access;
+ addrbook_reset();
+ init_ab_if_needed();
+ /* if we had trouble, give a clue to user (other than error msg) */
+ if(remember_access_result == NoAccess){
+ pab = &as.adrbks[num_in_list];
+ pab->access = remember_access_result;
+ }
+ }
+ }
+
+get_out:
+
+ if(he)
+ free_headents(&he);
+
+ if(new_list)
+ free_list_array(&new_list);
+
+ if(new_item)
+ fs_give((void **)&new_item);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(server)
+ fs_give((void **)&server);
+ if(folder)
+ fs_give((void **)&folder);
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ return(ret);
+}
+
+
+int
+any_addrbooks_to_convert(struct pine *ps)
+{
+ PerAddrBook *pab;
+ int i, count = 0;
+
+ init_ab_if_needed();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ count++;
+ }
+
+ return(count);
+}
+
+
+int
+convert_addrbooks_to_remote(struct pine *ps, char *rem_folder_prefix, size_t len)
+{
+ PerAddrBook *pab;
+ int i, count = 0, ret = 0;
+
+ init_ab_if_needed();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ count++;
+ }
+
+ for(i = 0; ret != -1 && i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ ret = convert_abook_to_remote(ps, pab, rem_folder_prefix, len, count);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns -1 if cancelled, -2 on error, 0 otherwise.
+ */
+int
+convert_abook_to_remote(struct pine *ps, PerAddrBook *pab, char *rem_folder_prefix, size_t len, int count)
+{
+#define DEF_ABOOK_NAME "remote_addrbook"
+ char local_file[MAILTMPLEN];
+ char rem_abook[MAILTMPLEN+3], prompt[MAILTMPLEN], old_nick[MAILTMPLEN];
+ char *p = NULL, *err_msg = NULL, *q;
+ char *serv = NULL, *nick = NULL, *file = NULL, *folder = NULL;
+ int ans, rc, offset, i, abook_num = -1, flags = OE_APPEND_CURRENT;
+ HelpType help;
+
+ snprintf(old_nick, sizeof(old_nick), "%s%s%s",
+ count > 1 ? " \"" : "",
+ count > 1 ? pab->abnick : "",
+ count > 1 ? "\"" : "");
+ old_nick[sizeof(old_nick)-1] = '\0';
+
+ snprintf(prompt, sizeof(prompt), _("Convert addressbook%s to a remote addrbook "), old_nick);
+ prompt[sizeof(prompt)-1] = '\0';
+ if((ans=want_to(prompt, 'y', 'x', h_convert_abook, WT_NORM)) != 'y')
+ return(ans == 'n' ? 0 : -1);
+
+ /* make sure the addrbook has been opened before, so that the file exists */
+ if(pab->ostatus == Closed || pab->ostatus == HalfOpen){
+ (void)init_addrbooks(NoDisplay, 0, 0, 0);
+ (void)init_addrbooks(Closed, 0, 0, 0);
+ }
+
+ if(pab->filename){
+ strncpy(local_file, pab->filename, sizeof(local_file)-1);
+ local_file[sizeof(local_file)-1] = '\0';
+#if defined(DOS)
+ p = strrindex(pab->filename, '\\');
+#else
+ p = strrindex(pab->filename, '/');
+#endif
+ }
+
+ strncpy(rem_abook, rem_folder_prefix, sizeof(rem_abook)-3);
+ if(!*rem_abook){
+ /* TRANSLATORS: The user is defining an address book which will be
+ stored on another server. This is called a remote addrbook and
+ this is a question asking for the name of the server. */
+ snprintf(prompt, sizeof(prompt), _("Name of server to contain remote addrbook : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_abook, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_abook), prompt, NULL,
+ help, &flags);
+ removing_leading_and_trailing_white_space(rem_abook);
+ if(rc == 3){
+ help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
+ }
+ else if(rc == 1){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ else if(rc == 0){
+ if(*rem_abook){
+ /* add brackets */
+ offset = strlen(rem_abook);
+ for(i = offset; i >= 0; i--)
+ rem_abook[i+1] = rem_abook[i];
+
+ rem_abook[0] = '{';
+ rem_abook[++offset] = '}';
+ rem_abook[++offset] = '\0';
+ break;
+ }
+ }
+ }
+ }
+
+ if(*rem_abook){
+ if(p && count > 1)
+ strncat(rem_abook, p+1,
+ sizeof(rem_abook)-1-strlen(rem_abook));
+ else
+ strncat(rem_abook, DEF_ABOOK_NAME,
+ sizeof(rem_abook)-1-strlen(rem_abook));
+ }
+
+ if(*rem_abook){
+ file = cpystr(rem_abook);
+ if(pab->abnick){
+ nick = (char *)fs_get((MAX(strlen(pab->abnick),strlen("Address Book"))+8) * sizeof(char));
+ snprintf(nick, sizeof(nick), "Remote %s",
+ (pab->abnick && !strcmp(pab->abnick, DF_ADDRESSBOOK))
+ ? "Address Book" : pab->abnick);
+ nick[sizeof(nick)-1] = '\0';
+ }
+ else
+ nick = cpystr("Remote Address Book");
+
+ if(file && *file == '{'){
+ q = file + 1;
+ if((p = strindex(file, '}'))){
+ *p = '\0';
+ serv = q;
+ folder = p+1;
+ }
+ else if(file)
+ fs_give((void **)&file);
+ }
+ else
+ folder = file;
+ }
+
+ q_status_message(SM_ORDER, 3, 5,
+ _("You now have a chance to change the name of the remote addrbook..."));
+ abook_num = ab_modify_abook_list(0, 0, -1, serv, folder, nick);
+
+ /* extract folder name of new abook so we can copy to it */
+ if(abook_num >= 0){
+ char **lval;
+ EditWhich ew = Main;
+
+ lval = LVAL(&ps->vars[V_ADDRESSBOOK], ew);
+ get_pair(lval[abook_num], &nick, &file, 0, 0);
+ if(nick)
+ fs_give((void **)&nick);
+
+ if(file){
+ strncpy(rem_abook, file, sizeof(rem_abook)-1);
+ rem_abook[sizeof(rem_abook)-1] = '\0';
+ fs_give((void **)&file);
+ }
+ }
+
+ /* copy the abook */
+ if(abook_num >= 0 && copy_abook(local_file, rem_abook, &err_msg)){
+ if(err_msg){
+ q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
+ fs_give((void **)&err_msg);
+ }
+
+ return(-2);
+ }
+ else if(abook_num >= 0){ /* give user some info */
+ STORE_S *store;
+ SCROLL_S sargs;
+ char *beg, *end;
+
+ /*
+ * Save the hostname in rem_folder_prefix so we can use it again
+ * for other conversions if needed.
+ */
+ if((beg = rem_abook)
+ && (*beg == '{' || (*beg == '*' && *++beg == '{'))
+ && (end = strindex(rem_abook, '}'))){
+ rem_folder_prefix[0] = '{';
+ strncpy(rem_folder_prefix+1, beg+1, MIN(end-beg,len-2));
+ rem_folder_prefix[MIN(end-beg,len-2)] = '}';
+ rem_folder_prefix[MIN(end-beg+1,len-1)] = '\0';
+ }
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-2);
+ }
+
+ /* TRANSLATORS: Several lines in a row here that go together. */
+ snprintf(prompt, sizeof(prompt), _("\nYour addressbook%s has been copied to the"), old_nick);
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store, _("\nremote folder \""));
+ so_puts(store, rem_abook);
+ so_puts(store, "\".");
+ so_puts(store, _("\nA definition for this remote address book has been added to your list"));
+ so_puts(store, _("\nof address books. The definition for the address book it was copied"));
+ so_puts(store, _("\nfrom is also still there. You may want to remove that after you"));
+ so_puts(store, _("\nare confident that the new address book is complete and working."));
+ so_puts(store, _("\nUse the Setup/AddressBooks command to do that.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Remote Address Book Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT REMOTE ABOOK");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps->mangled_screen = 1;
+ }
+
+ return(0);
+}
+
+
+int
+any_sigs_to_convert(struct pine *ps)
+{
+ char *sigfile, *litsig;
+ long rflags;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ PAT_LINE_S *patline;
+
+ /* first check main signature file */
+ sigfile = ps->VAR_SIGNATURE_FILE;
+ litsig = ps->VAR_LITERAL_SIG;
+
+ if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile))
+ return(1);
+
+ rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ set_pathandle(rflags);
+ for(patline = *cur_pat_h ? (*cur_pat_h)->patlinehead : NULL;
+ patline; patline = patline->next){
+ for(pat = patline->first; pat; pat = pat->next){
+
+ /*
+ * See detoken() for when a sig file is used with a role.
+ */
+ sigfile = pat->action ? pat->action->sig : NULL;
+ litsig = pat->action ? pat->action->litsig : NULL;
+
+ if(sigfile && *sigfile && !litsig &&
+ sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile))
+ return(1);
+ }
+ }
+ }
+
+ return(0);
+}
+
+
+int
+any_rule_files_to_warn_about(struct pine *ps)
+{
+ long rflags;
+ PAT_STATE pstate;
+ PAT_S *pat;
+
+ rflags = (ROLE_DO_ROLES | ROLE_DO_INCOLS | ROLE_DO_SCORES |
+ ROLE_DO_FILTER | ROLE_DO_OTHER | ROLE_DO_SRCH | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patline && pat->patline->type == File)
+ break;
+ }
+
+ if(pat)
+ return(1);
+ }
+
+ return(0);
+}
+
+
+int
+convert_sigs_to_literal(struct pine *ps, int interactive)
+{
+ EditWhich ew = Main;
+ char *sigfile, *litsig, *cstring_version, *nick, *src = NULL;
+ char prompt[MAILTMPLEN];
+ STORE_S *store;
+ SCROLL_S sargs;
+ long rflags;
+ int ans;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ PAT_LINE_S *patline;
+
+ /* first check main signature file */
+ sigfile = ps->VAR_SIGNATURE_FILE;
+ litsig = ps->VAR_LITERAL_SIG;
+
+ if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile)){
+ if(interactive){
+ snprintf(prompt,sizeof(prompt),
+ /* TRANSLATORS: A literal sig is a way to store a signature
+ in alpine. It isn't a very descriptive name. Instead of
+ storing it in its own file it is stored in the configuration
+ file. */
+ _("Convert signature file \"%s\" to a literal sig "),
+ sigfile);
+ prompt[sizeof(prompt)-1] = '\0';
+ ClearBody();
+ ps->mangled_body = 1;
+ if((ans=want_to(prompt, 'y', 'x', h_convert_sig, WT_NORM)) == 'x'){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ }
+ else
+ ans = 'y';
+
+ if(ans == 'y' && (src = get_signature_file(sigfile, 0, 0, 0)) != NULL){
+ cstring_version = string_to_cstring(src);
+ set_variable(V_LITERAL_SIG, cstring_version, 0, 0, ew);
+
+ if(cstring_version)
+ fs_give((void **)&cstring_version);
+
+ fs_give((void **)&src);
+
+ if(interactive){
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-1);
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ /* TRANSLATORS: following lines go together */
+ _("\nYour signature file \"%s\" has been converted"), sigfile);
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store,
+ _("\nto a literal signature, which means it is contained in your"));
+ so_puts(store,
+ _("\nAlpine configuration instead of being in a file of its own."));
+ so_puts(store,
+ _("\nIf that configuration is copied to a remote folder then the"));
+ so_puts(store,
+ _("\nsignature will be available remotely also."));
+ so_puts(store,
+ _("\nChanges to the signature file itself will no longer have any"));
+ so_puts(store,
+ _("\neffect on Alpine but you may still edit the signature with the"));
+ so_puts(store,
+ _("\nSetup/Signature command.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Literal Signature Information");
+ /* TRANSLATORS: screen title */
+ sargs.bar.title = _("ABOUT LITERAL SIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+ }
+ }
+
+ rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ set_pathandle(rflags);
+ for(patline = *cur_pat_h ? (*cur_pat_h)->patlinehead : NULL;
+ patline; patline = patline->next){
+ for(pat = patline->first; pat; pat = pat->next){
+
+ /*
+ * See detoken() for when a sig file is used with a role.
+ */
+ sigfile = pat->action ? pat->action->sig : NULL;
+ litsig = pat->action ? pat->action->litsig : NULL;
+ nick = (pat->action && pat->action->nick && pat->action->nick[0]) ? pat->action->nick : NULL;
+
+ if(sigfile && *sigfile && !litsig &&
+ sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile)){
+ if(interactive){
+ snprintf(prompt,sizeof(prompt),
+ /* TRANSLATORS: asking whether a signature file should be converted to what
+ we call a literal signature, which is one contained in the regular
+ configuration file. Think of the set of 4 %s arguments as a
+ single argument which is the name of the signature file. */
+ _("Convert signature file \"%s\"%s%s%s to a literal sig "),
+ sigfile,
+ nick ? " in role \"" : "",
+ nick ? nick : "",
+ nick ? "\"" : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ ClearBody();
+ ps->mangled_body = 1;
+ if((ans=want_to(prompt, 'y', 'x',
+ h_convert_sig, WT_NORM)) == 'x'){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ }
+ else
+ ans = 'y';
+
+ if(ans == 'y' &&
+ (src = get_signature_file(sigfile,0,0,0)) != NULL){
+
+ cstring_version = string_to_cstring(src);
+
+ if(pat->action->litsig)
+ fs_give((void **)&pat->action->litsig);
+
+ pat->action->litsig = cstring_version;
+ fs_give((void **)&src);
+
+ set_pathandle(rflags);
+ if(patline->type == Literal)
+ (*cur_pat_h)->dirtypinerc = 1;
+ else
+ patline->dirty = 1;
+
+ if(write_patterns(rflags) == 0){
+ if(interactive){
+ /*
+ * Flush out current_vals of anything we've
+ * possibly changed.
+ */
+ close_patterns(ROLE_DO_ROLES | PAT_USE_CURRENT);
+
+ if(!(store=so_get(CharStar,NULL,EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-1);
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ /* TRANSLATORS: Keep the %s's together, they are sort of
+ the name of the file. */
+ _("Your signature file \"%s\"%s%s%s has been converted"),
+ sigfile,
+ nick ? " in role \"" : "",
+ nick ? nick : "",
+ nick ? "\"" : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store,
+ /* TRANSLATORS: several lines that go together */
+ _("\nto a literal signature, which means it is contained in your"));
+ so_puts(store,
+ _("\nAlpine configuration instead of being in a file of its own."));
+ so_puts(store,
+ _("\nIf that configuration is copied to a remote folder then the"));
+ so_puts(store,
+ _("\nsignature will be available remotely also."));
+ so_puts(store,
+ _("\nChanges to the signature file itself will no longer have any"));
+ so_puts(store,
+ _("\neffect on Alpine. You may edit the signature with the"));
+ so_puts(store,
+ _("\nSetup/Rules/Roles command.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc =
+ _("Literal Signature Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT LITERAL SIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+ }
+ else if(interactive){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ /* TRANSLATORS: config is an abbreviation for configuration */
+ _("Error writing rules config."));
+ }
+ else{
+ /* TRANSLATORS: sig is signature */
+ fprintf(stderr, _("Error converting role sig\n"));
+ return(-1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return(0);
+}
+
+
+void
+warn_about_rule_files(struct pine *ps)
+{
+ STORE_S *store;
+ SCROLL_S sargs;
+
+ if(any_rule_files_to_warn_about(ps)){
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ /* TRANSLATORS: several lines that go together */
+ so_puts(store, _("\nSome of your Rules are contained in Rule files instead of being directly"));
+ so_puts(store, _("\ncontained in your Alpine configuration file. To make those rules"));
+ so_puts(store, _("\navailable remotely you will need to move them out of the files."));
+ so_puts(store, _("\nThat can be done using the Shuffle command in the appropriate"));
+ so_puts(store, _("\nSetup/Rules subcommands.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Rule Files Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT RULE FILES");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+}
+
+
+void
+convert_to_remote_config(struct pine *ps, int edit_exceptions)
+{
+ char rem_pinerc_prefix[MAILTMPLEN];
+ char *beg, *end;
+ CONTEXT_S *context;
+ int abooks, sigs;
+
+ if(edit_exceptions){
+ /* TRANSLATORS: The exceptions command (X) was typed but it doesn't make sense */
+ q_status_message(SM_ORDER, 3, 5,
+ _("eXceptions does not make sense with this command"));
+ return;
+ }
+
+ if(!ps->prc)
+ panic("NULL prc in convert_to_remote_config");
+
+ dprint((2, "convert_to_remote_config\n"));
+
+ if(ps->prc->type == RemImap){ /* pinerc is already remote */
+ char prompt[MAILTMPLEN];
+
+ /*
+ * Check to see if there is anything at all to do. If there are
+ * address books to convert, sigfiles to convert, or rule files
+ * to comment on, we have something to do. Otherwise, just bail.
+ */
+ abooks = any_addrbooks_to_convert(ps);
+ sigs = any_sigs_to_convert(ps);
+
+ if(abooks || sigs){
+ if(abooks && sigs)
+ /* TRANSLATORS: AddressBooks is Address Books */
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks and signature files "));
+ else if(abooks)
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert signature files "));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ if(want_to(prompt, 'y', 'x',
+ (abooks && sigs) ? h_convert_abooks_and_sigs :
+ abooks ? h_convert_abooks :
+ sigs ? h_convert_sigs : NO_HELP,
+ WT_NORM) != 'y'){
+ cmd_cancelled(NULL);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Figure out a good default for where to put the remote config.
+ * If the default collection is remote we'll take the hostname and
+ * and modifiers from there. If not, we'll try to get the hostname from
+ * the inbox-path. In either case, we use the home directory on the
+ * server, not the directory where the folder collection is (if different).
+ * If we don't have a clue, we'll ask user.
+ */
+ if((context = default_save_context(ps->context_list)) != NULL &&
+ IS_REMOTE(context_apply(rem_pinerc_prefix, context, "",
+ sizeof(rem_pinerc_prefix)))){
+ /* just use the host from the default collection, not the whole path */
+ if((end = strrindex(rem_pinerc_prefix, '}')) != NULL)
+ *(end + 1) = '\0';
+ }
+ else{
+ /* use host from inbox path */
+ rem_pinerc_prefix[0] = '\0';
+ if((beg = ps->VAR_INBOX_PATH)
+ && (*beg == '{' || (*beg == '*' && *++beg == '{'))
+ && (end = strindex(ps->VAR_INBOX_PATH, '}'))){
+ rem_pinerc_prefix[0] = '{';
+ strncpy(rem_pinerc_prefix+1, beg+1,
+ MIN(end-beg, sizeof(rem_pinerc_prefix)-2));
+ rem_pinerc_prefix[MIN(end-beg, sizeof(rem_pinerc_prefix)-2)] = '}';
+ rem_pinerc_prefix[MIN(end-beg+1,sizeof(rem_pinerc_prefix)-1)]='\0';
+ }
+ }
+
+ /* ask about converting addrbooks to remote abooks */
+ if(ps->prc->type != RemImap || abooks)
+ if(convert_addrbooks_to_remote(ps, rem_pinerc_prefix,
+ sizeof(rem_pinerc_prefix)) == -1){
+ cmd_cancelled(NULL);
+ return;
+ }
+
+ /* ask about converting sigfiles to literal sigs */
+ if(ps->prc->type != RemImap || sigs)
+ if(convert_sigs_to_literal(ps, 1) == -1){
+ cmd_cancelled(NULL);
+ return;
+ }
+
+ warn_about_rule_files(ps);
+
+ /* finally, copy the config file */
+ if(ps->prc->type == Loc)
+ convert_pinerc_to_remote(ps, rem_pinerc_prefix);
+ else if(!(abooks || sigs))
+ q_status_message(SM_ORDER, 3, 5,
+ _("Cannot copy config file since it is already remote."));
+}
+
+
+void
+convert_pinerc_to_remote(struct pine *ps, char *rem_pinerc_prefix)
+{
+#define DEF_FOLDER_NAME "remote_pinerc"
+ char prompt[MAILTMPLEN], rem_pinerc[MAILTMPLEN];
+ char *err_msg = NULL;
+ int i, rc, offset;
+ HelpType help;
+ int flags = OE_APPEND_CURRENT;
+
+ ClearBody();
+ ps->mangled_body = 1;
+ strncpy(rem_pinerc, rem_pinerc_prefix, sizeof(rem_pinerc)-1);
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+
+ if(*rem_pinerc == '\0'){
+ snprintf(prompt, sizeof(prompt), _("Name of server to contain remote Alpine config : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_pinerc), prompt, NULL,
+ help, &flags);
+ removing_leading_and_trailing_white_space(rem_pinerc);
+ if(rc == 3){
+ help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
+ }
+ else if(rc == 1){
+ cmd_cancelled(NULL);
+ return;
+ }
+ else if(rc == 0){
+ if(*rem_pinerc){
+ /* add brackets */
+ offset = strlen(rem_pinerc);
+ for(i = offset; i >= 0; i--)
+ if(i+1 < sizeof(rem_pinerc))
+ rem_pinerc[i+1] = rem_pinerc[i];
+
+ rem_pinerc[0] = '{';
+ if(offset+2 < sizeof(rem_pinerc)){
+ rem_pinerc[++offset] = '}';
+ rem_pinerc[++offset] = '\0';
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+
+ /*
+ * Add a default folder name.
+ */
+ if(*rem_pinerc){
+ /*
+ * Add /user= to modify hostname so that user won't be asked who they
+ * are each time they login.
+ */
+ if(!strstr(rem_pinerc, "/user=") && ps->VAR_USER_ID &&
+ ps->VAR_USER_ID[0]){
+ char *p;
+
+ p = rem_pinerc + strlen(rem_pinerc) - 1;
+ if(*p == '}') /* this should be the case */
+ snprintf(p, sizeof(rem_pinerc)-(p-rem_pinerc), "/user=\"%s\"}", ps->VAR_USER_ID);
+
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+ }
+
+ strncat(rem_pinerc, DEF_FOLDER_NAME,
+ sizeof(rem_pinerc) - strlen(rem_pinerc) - 1);
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+ }
+
+ /* ask user about folder name for remote config */
+ snprintf(prompt, sizeof(prompt), _("Folder to contain remote config : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_pinerc), prompt, NULL, help, &flags);
+ removing_leading_and_trailing_white_space(rem_pinerc);
+ if(rc == 0 && *rem_pinerc){
+ break;
+ }
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_convert_pinerc_folder : NO_HELP;
+ }
+ else if(rc == 1 || rem_pinerc[0] == '\0'){
+ cmd_cancelled(NULL);
+ return;
+ }
+ }
+
+#ifndef _WINDOWS
+ /*
+ * If we are on a Unix system, writing to a remote config, we want the
+ * remote config to work smoothly from a PC, too. If we don't have a
+ * user-id on the PC then we will be asked for our password.
+ * So add user-id to the pinerc before we copy it.
+ */
+ if(!ps->vars[V_USER_ID].main_user_val.p && ps->VAR_USER_ID)
+ ps->vars[V_USER_ID].main_user_val.p = cpystr(ps->VAR_USER_ID);
+
+ ps->vars[V_USER_ID].is_used = 1; /* so it will write to pinerc */
+ ps->prc->outstanding_pinerc_changes = 1;
+#endif
+
+ if(ps->prc->outstanding_pinerc_changes)
+ write_pinerc(ps, Main, WRP_NONE);
+
+#ifndef _WINDOWS
+ ps->vars[V_USER_ID].is_used = 0;
+#endif
+
+ /* copy the pinerc */
+ if(copy_pinerc(ps->prc->name, rem_pinerc, &err_msg)){
+ if(err_msg){
+ q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
+ fs_give((void **)&err_msg);
+ }
+
+ return;
+ }
+
+ /* tell user about command line flags */
+ if(ps->prc->type != RemImap){
+ STORE_S *store;
+ SCROLL_S sargs;
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ /* TRANSLATORS: several lines that go together */
+ so_puts(store, _("\nYou may want to save a copy of this information!"));
+ so_puts(store, _("\n\nYour Alpine configuration data has been copied to"));
+ so_puts(store, "\n\n ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nTo use that remote configuration from this computer you will"));
+ so_puts(store, _("\nhave to change the way you start Alpine by using the command line option"));
+ so_puts(store, _("\n\"alpine -p <remote_folder>\". The command should probably be"));
+#ifdef _WINDOWS
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nWith PC-Alpine, you may want to create a shortcut which"));
+ so_puts(store, _("\nhas the required arguments."));
+#else
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p \"");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\"\n");
+ so_puts(store, _("\nThe quotes are there around the last argument to protect the special"));
+ so_puts(store, _("\ncharacters in the folder name (like braces) from the command shell"));
+ so_puts(store, _("\nyou use. If you are not running Alpine from a command shell which knows"));
+ so_puts(store, _("\nabout quoting, it is possible you will have to remove those quotes"));
+ so_puts(store, _("\nfrom the command. For example, if you also use PC-Alpine you will probably"));
+ so_puts(store, _("\nwant to create a shortcut, and you would not need the quotes there."));
+ so_puts(store, _("\nWithout the quotes, the command might look like"));
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nConsider creating an alias or shell script to execute this command to make"));
+ so_puts(store, _("\nit more convenient."));
+#endif
+ so_puts(store, _("\n\nIf you want to use your new remote configuration for this session, quit"));
+ so_puts(store, _("\nAlpine now and restart with the changed command line options mentioned above.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Remote Config Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT REMOTE CONFIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps->mangled_screen = 1;
+ }
+}
+
+
+int
+verify_folder_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+int
+verify_server_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(*tmp){
+ /*
+ * could try to verify the hostname here
+ */
+ }
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+int
+verify_abook_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ int i;
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(strindex(tmp, '"')){
+ fs_give((void **)&tmp);
+ if(error)
+ /* TRANSLATORS: Double quote refers to the " character */
+ *error = cpystr(_("Double quote not allowed in nickname"));
+
+ return -2;
+ }
+
+ for(i = 0; i < as.n_addrbk; i++)
+ if(i != the_one_were_editing && !strcmp(tmp, as.adrbks[i].abnick))
+ break;
+
+ if(i < as.n_addrbk){
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr(_("Nickname is already being used"));
+
+ return -2;
+ }
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+/*
+ * Delete an addressbook.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ * err -- Points to error message
+ *
+ * Returns -- 0, deleted addrbook
+ * -1, addrbook not deleted
+ */
+int
+ab_del_abook(long int cur_line, int command_line, char **err)
+{
+ int abook_num, varnum, delete_data = 0,
+ num_in_list, how_many_in_list, i, cnt, warn_about_revert = 0;
+ char **list, **new_list, **t, **lval;
+ char tmp[200];
+ PerAddrBook *pab;
+ struct variable *vars = ps_global->vars;
+ EditWhich ew;
+ enum {NotSet,
+ Modify,
+ RevertToDefault,
+ OverRideDefault,
+ DontChange} modify_config;
+
+ /* restrict address book config to normal config file */
+ ew = Main;
+
+ if(ps_global->readonly_pinerc){
+ if(err)
+ *err = _("Delete cancelled: config file not changeable");
+
+ return -1;
+ }
+
+ abook_num = adrbk_num_from_lineno(cur_line);
+
+ pab = &as.adrbks[abook_num];
+
+ dprint((2, "- ab_del_abook(%s) -\n",
+ pab->abnick ? pab->abnick : "?"));
+
+ varnum = (pab->type & GLOBAL) ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK;
+
+ if(vars[varnum].is_fixed){
+ if(err){
+ if(pab->type & GLOBAL)
+ *err =
+ _("Cancelled: Sys. Mgmt. does not allow changing global address book config");
+ else
+ *err =
+ _("Cancelled: Sys. Mgmt. does not allow changing address book config");
+ }
+
+ return -1;
+ }
+
+ /*
+ * Deal with reverting to default values of the address book
+ * variables, or with user deleting a default value.
+ */
+ modify_config = NotSet;
+
+ /* First count how many address books are in the user's config. */
+ cnt = 0;
+ lval = LVAL(&vars[varnum], ew);
+ if(lval && lval[0])
+ for(t = lval; *t != NULL; t++)
+ cnt++;
+
+ /*
+ * Easy case, we can just delete one from the user's list.
+ */
+ if(cnt > 1){
+ modify_config = Modify;
+ }
+ /*
+ * Also easy. We'll revert to the default if it exists, and warn
+ * the user about that.
+ */
+ else if(cnt == 1){
+ modify_config = RevertToDefault;
+ /* see if there's a default to revert to */
+ cnt = 0;
+ if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
+ for(t = vars[varnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ warn_about_revert = cnt;
+ }
+ /*
+ * User is already using the default. Split it into two cases. If there
+ * is one address book in default ask user if they want to delete that
+ * default from their config. If there is more than one, ask them if
+ * they want to ignore all the defaults or just delete this one.
+ */
+ else{
+ /* count how many in default */
+ cnt = 0;
+ if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
+ for(t = vars[varnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ if(cnt > 1){
+ static ESCKEY_S opts[] = {
+ /* TRANSLATORS: Ignore All means ignore all of the default values,
+ and Remove One means just remove this one default value. */
+ {'i', 'i', "I", N_("Ignore All")},
+ {'r', 'r', "R", N_("Remove One")},
+ {-1, 0, NULL, NULL}};
+
+ snprintf(tmp, sizeof(tmp),
+ /* TRANSLATORS: %s is an adjective modifying address books */
+ _("Ignore all default %s address books or just remove this one ? "),
+ /* TRANSLATORS: global or personal address books */
+ pab->type & GLOBAL ? _("global") : _("personal"));
+ tmp[sizeof(tmp)-1] = '\0';
+ switch(radio_buttons(tmp, command_line, opts, 'i', 'x',
+ h_ab_del_ignore, RB_NORM)){
+ case 'i':
+ modify_config = OverRideDefault;
+ break;
+
+ case 'r':
+ modify_config = Modify;
+ break;
+
+ case 'x':
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+ else{
+ /* TRANSLATORS: a question */
+ switch(want_to(_("Delete this default address book from config "),
+ 'n', 'x', h_ab_del_default, WT_NORM)){
+ case 'n':
+ case 'x':
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+
+ case 'y':
+ modify_config = OverRideDefault;
+ break;
+ }
+ }
+ }
+
+ /*
+ * ReadWrite means it exists and MaybeRorW means it is remote and we
+ * haven't selected it yet to know our access permissions. The remote
+ * folder should have been created, though, unless we didn't even have
+ * permissions for that, in which case we got some error messages earlier.
+ */
+ if(pab->access == ReadWrite || pab->access == MaybeRorW){
+ static ESCKEY_S o[] = {
+ /* TRANSLATORS: user is asked whether to remove just data files, just configuration,
+ or both for an address book. */
+ {'d', 'd', "D", N_("Data")},
+ {'c', 'c', "C", N_("Config")},
+ {'b', 'b', "B", N_("Both")},
+ {-1, 0, NULL, NULL}};
+
+ switch(radio_buttons(_("Delete data, config, or both ? "),
+ command_line, o, 'c', 'x',
+ (modify_config == RevertToDefault)
+ ? h_ab_del_data_revert
+ : h_ab_del_data_modify,
+ RB_NORM)){
+ case 'b': /* Delete Both */
+ delete_data = 1;
+ break;
+
+ case 'd': /* Delete only Data */
+ modify_config = DontChange;
+ delete_data = 1;
+ break;
+
+ case 'c': /* Delete only Config */
+ break;
+
+ case 'x': /* Cancel */
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+ else{
+ /*
+ * Deleting config for address book which doesn't yet exist (hasn't
+ * ever been opened).
+ */
+ /* TRANSLATORS: a question */
+ switch(want_to(_("Delete configuration for highlighted addressbook "),
+ 'n', 'x',
+ (modify_config == RevertToDefault)
+ ? h_ab_del_config_revert
+ : h_ab_del_config_modify,
+ WT_NORM)){
+ case 'n':
+ case 'x':
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+
+ case 'y':
+ break;
+ }
+ }
+
+ if(delete_data){
+ char warning[800];
+
+ dprint((5, "deleting addrbook data\n"));
+ warning[0] = '\0';
+
+ /*
+ * In order to delete the address book it is easiest if we open
+ * it first. That fills in the filenames we want to delete.
+ */
+ if(pab->address_book == NULL){
+ warning[300] = '\0';
+ pab->address_book = adrbk_open(pab, ps_global->home_dir,
+ &warning[300], sizeof(warning)-300,
+ AB_SORT_RULE_NONE);
+ /*
+ * Couldn't get it open.
+ */
+ if(pab->address_book == NULL){
+ if(warning[300])
+ /* TRANSLATORS: %s is an error message */
+ snprintf(warning, 300, _("Can't delete data: %s"), &warning[300]);
+ else
+ strncpy(warning, _("Can't delete address book data"), 100);
+ }
+ }
+
+ /*
+ * If we have it open, set the delete bits and close to get the
+ * local copies. Delete the remote folder by hand.
+ */
+ if(pab->address_book){
+ char *file, *origfile = NULL;
+ int f=0, o=0;
+
+ /*
+ * We're about to destroy addrbook data, better ask again.
+ */
+ if(pab->address_book->count > 0){
+ char prompt[100];
+
+ /* TRANSLATORS: a question */
+ snprintf(prompt, sizeof(prompt),
+ _("About to delete the contents of address book (%ld entries), really delete "), (long) adrbk_count(pab->address_book));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ switch(want_to(prompt, 'n', 'n', h_ab_really_delete, WT_NORM)){
+ case 'y':
+ break;
+
+ case 'n':
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+
+ pab->address_book->flags |= DEL_FILE;
+ file = cpystr(pab->address_book->filename);
+ if(pab->type & REMOTE_VIA_IMAP)
+ origfile = cpystr(pab->address_book->orig_filename);
+
+ /*
+ * In order to avoid locking problems when we delete the
+ * remote folder, we need to actually close the remote stream
+ * instead of just putting it back in the stream pool.
+ * So we will remove this stream from the re-usable portion
+ * of the stream pool by clearing the SP_USEPOOL flag.
+ * Init_abook(pab, TotallyClosed) via rd_close_remdata is
+ * going to pine_mail_close it.
+ */
+ if(pab->type && REMOTE_VIA_IMAP
+ && pab->address_book
+ && pab->address_book->type == Imap
+ && pab->address_book->rd
+ && rd_stream_exists(pab->address_book->rd)){
+
+ sp_unflag(pab->address_book->rd->t.i.stream, SP_USEPOOL);
+ }
+
+ /* This deletes the files because of DEL_ bits we set above. */
+ init_abook(pab, TotallyClosed);
+
+ /*
+ * Delete the remote folder.
+ */
+ if(pab->type & REMOTE_VIA_IMAP){
+ REMDATA_S *rd;
+ int exists;
+
+ ps_global->c_client_error[0] = '\0';
+ if(!pine_mail_delete(NULL, origfile) &&
+ ps_global->c_client_error[0] != '\0'){
+ dprint((1, "%s: %s\n", origfile ? origfile : "?",
+ ps_global->c_client_error));
+ }
+
+ /* delete line from metadata */
+ rd = rd_new_remdata(RemImap, origfile, NULL);
+ rd_write_metadata(rd, 1);
+ rd_close_remdata(&rd);
+
+ /* Check to see if it's still there */
+ if((exists=folder_exists(NULL, origfile)) &&
+ (exists != FEX_ERROR)){
+ o++;
+ dprint((1, "Trouble deleting %s\n",
+ origfile ? origfile : "?"));
+ }
+ }
+
+ if(can_access(file, ACCESS_EXISTS) == 0){
+ f++;
+ dprint((1, "Trouble deleting %s\n",
+ file ? file : "?"));
+ }
+
+ if(f || o){
+ snprintf(warning, sizeof(warning), _("Trouble deleting data %s%s%s%s"),
+ f ? file : "",
+ (f && o) ? (o ? ", " : " and ") : "",
+ o ? " and " : "",
+ o ? origfile : "");
+ warning[sizeof(warning)-1] = '\0';
+ }
+
+ fs_give((void **) &file);
+ if(origfile)
+ fs_give((void **) &origfile);
+ }
+
+ if(*warning){
+ q_status_message(SM_ORDER, 3, 3, warning);
+ dprint((1, "%s\n", warning));
+ display_message(NO_OP_COMMAND);
+ }
+ else if(modify_config == DontChange)
+ q_status_message(SM_ORDER, 0, 1, _("Addressbook data deleted"));
+ }
+
+ if(modify_config == DontChange){
+ /*
+ * We return -1 to indicate that the addrbook wasn't deleted (as far
+ * as we're concerned) but we don't fill in err so that no error
+ * message will be printed.
+ * Since the addrbook is still an addrbook we need to reinitialize it.
+ */
+ pab->access = adrbk_access(pab);
+ if(pab->type & GLOBAL && pab->access != NoAccess)
+ pab->access = ReadOnly;
+
+ init_abook(pab, HalfOpen);
+ return -1;
+ }
+ else if(modify_config == Modify){
+ list = vars[varnum].current_val.l;
+ if(pab->type & GLOBAL){
+ how_many_in_list = as.n_addrbk - as.how_many_personals - 1;
+ num_in_list = abook_num - as.how_many_personals;
+ }
+ else{
+ how_many_in_list = as.how_many_personals - 1;
+ num_in_list = abook_num;
+ }
+ }
+ else if(modify_config == OverRideDefault)
+ how_many_in_list = 1;
+ else if(modify_config == RevertToDefault)
+ how_many_in_list = 0;
+ else
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_del_abook");
+
+ /* allocate for new list */
+ if(how_many_in_list)
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+ else
+ new_list = NULL;
+
+ /*
+ * This case is both for modifying the users user_val and for the
+ * case where the user wants to modify the global_val default and
+ * use the modified version for his or her new user_val. We just
+ * copy from the existing global_val, deleting the one addrbook
+ * and put the result in user_val.
+ */
+ if(modify_config == Modify){
+ /* copy old list up to where we will delete entry */
+ for(i = 0; i < num_in_list; i++)
+ new_list[i] = cpystr(list[i]);
+
+ /* copy rest of old list */
+ for(; i < how_many_in_list; i++)
+ new_list[i] = cpystr(list[i+1]);
+
+ new_list[i] = NULL;
+ }
+ else if(modify_config == OverRideDefault){
+ new_list[0] = cpystr("");
+ new_list[1] = NULL;
+ }
+
+ /* this also frees old variable contents for us */
+ if(set_variable_list(varnum, new_list, TRUE, ew)){
+ if(err)
+ *err = _("Delete cancelled: couldn't save pine configuration file");
+
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+
+ return -1;
+ }
+
+ set_current_val(&vars[varnum], TRUE, FALSE);
+
+ if(warn_about_revert){
+ /* TRANSLATORS: the %s may be "global " or nothing */
+ snprintf(tmp, sizeof(tmp), _("Reverting to default %saddress books"),
+ pab->type & GLOBAL ? _("global ") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 4, tmp);
+ }
+
+ free_list_array(&new_list);
+
+ return 0;
+}
+
+
+/*
+ * Shuffle addrbooks.
+ *
+ * Args: pab -- Pab from current addrbook.
+ * slide -- return value, tells how far to slide the cursor. If slide
+ * is negative, slide it up, if positive, slide it down.
+ * command_line -- The screen line on which to prompt
+ * msg -- Points to returned message, if any. Should be freed by
+ * caller.
+ *
+ * Result: Two address books are swapped in the display order. If the shuffle
+ * crosses the Personal/Global boundary, then instead of swapping
+ * two address books the highlighted abook is moved from one section
+ * to the other.
+ * >= 0 on success.
+ * < 0 on failure, no changes.
+ * > 0 If the return value is greater than zero it means that we've
+ * reverted one of the variables to its default value. That
+ * means we've added at least one new addrbook, so the caller
+ * should reset. The value returned is the number of the
+ * moved addrbook + 1 (+1 so it won't be confused with zero).
+ * = 0 If the return value is zero we've just moved addrbooks around.
+ * No reset need be done.
+ */
+int
+ab_shuffle(PerAddrBook *pab, int *slide, int command_line, char **msg)
+{
+ ESCKEY_S opts[3];
+ char tmp[200];
+ int i, deefault, rv, target = 0;
+ int up_into_empty = 0, down_into_empty = 0;
+ HelpType help;
+ struct variable *vars = ps_global->vars;
+
+ dprint((2, "- ab_shuffle() -\n"));
+
+ *slide = 0;
+
+ if(ps_global->readonly_pinerc){
+ if(msg)
+ *msg = cpystr(_("Shuffle cancelled: config file not changeable"));
+
+ return -1;
+ }
+
+ /* Move it up or down? */
+ i = 0;
+ opts[i].ch = 'u';
+ opts[i].rval = 'u';
+ opts[i].name = "U";
+ /* TRANSLATORS: shuffle something Up or Down in a list */
+ opts[i++].label = N_("Up");
+
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i++].label = N_("Down");
+
+ opts[i].ch = -1;
+ deefault = 'u';
+
+ if(pab->type & GLOBAL){
+ if(vars[V_GLOB_ADDRBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));
+
+ return -1;
+ }
+
+ if(as.cur == 0){
+ if(as.config)
+ up_into_empty++;
+ else{ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+ }
+
+ if(as.cur == as.n_addrbk - 1) /* no down */
+ opts[1].ch = -2;
+ }
+ else{
+ if(vars[V_ADDRESSBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book config"));
+
+ return -1;
+ }
+
+ if(as.cur == 0){ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+
+ if(as.cur == as.n_addrbk - 1){
+ if(as.config)
+ down_into_empty++;
+ else
+ opts[1].ch = -2; /* no down */
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), _("Shuffle \"%s\" %s%s%s ? "),
+ pab->abnick,
+ (opts[0].ch != -2) ? _("UP") : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? _("DOWN") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = (opts[0].ch == -2) ? h_ab_shuf_down
+ : (opts[1].ch == -2) ? h_ab_shuf_up
+ : h_ab_shuf;
+
+ rv = radio_buttons(tmp, command_line, opts, deefault, 'x',
+ help, RB_NORM);
+
+ ps_global->mangled_footer = 1;
+
+ if((rv == 'u' && up_into_empty) || (rv == 'd' && down_into_empty))
+ target = -1;
+ else
+ target = as.cur + (rv == 'u' ? -1 : 1);
+
+ if(rv == 'x'){
+ if(msg)
+ *msg = cpystr(_("Shuffle cancelled"));
+
+ return -1;
+ }
+ else
+ return(do_the_shuffle(slide, as.cur, target, msg));
+}
+
+
+/*
+ * Actually shuffle the config variables and address books structures around.
+ *
+ * Args: anum1, anum2 -- The numbers of the address books
+ * msg -- Points to returned message, if any.
+ *
+ * Returns: >= 0 on success.
+ * < 0 on failure, no changes.
+ * > 0 If the return value is greater than zero it means that we've
+ * reverted one of the variables to its default value. That
+ * means we've added at least one new addrbook, so the caller
+ * should reset. The value returned is the number of the
+ * moved addrbook + 1 (+1 so it won't be confused with zero).
+ * = 0 If the return value is zero we've just moved addrbooks around.
+ * No reset need be done.
+ *
+ * Anum1 is the one that we want to move, anum2 is the one that it will be
+ * swapped with. When anum1 and anum2 are on the opposite sides of the
+ * Personal/Global boundary then instead of swapping we just move anum1 to
+ * the other side of the boundary.
+ *
+ * Anum2 of -1 means it is a swap into the other type of address book, which
+ * is currently empty.
+ */
+int
+do_the_shuffle(int *slide, int anum1, int anum2, char **msg)
+{
+ PerAddrBook *pab;
+ enum {NotSet, Pers, Glob, Empty} type1, type2;
+ int i, j, retval = -1;
+ struct variable *vars = ps_global->vars;
+ char **lval;
+ EditWhich ew;
+ char *cancel_msg = _("Shuffle cancelled: couldn't save configuration file");
+
+ dprint((5, "- do_the_shuffle(%d, %d) -\n", anum1, anum2));
+
+ /* restrict address book config to normal config file */
+ ew = Main;
+
+ if(anum1 == -1)
+ type1 = Empty;
+ else{
+ pab = &as.adrbks[anum1];
+ type1 = (pab->type & GLOBAL) ? Glob : Pers;
+ }
+
+ if(type1 == Empty){
+ if(msg)
+ *msg =
+ cpystr(_("Shuffle cancelled: highlight entry you wish to shuffle"));
+
+ return(retval);
+ }
+
+ if(anum2 == -1)
+ type2 = Empty;
+ else{
+ pab = &as.adrbks[anum2];
+ type2 = (pab->type & GLOBAL) ? Glob : Pers;
+ }
+
+ if(type2 == Empty)
+ type2 = (type1 == Pers) ? Glob : Pers;
+
+ if((type1 == Pers || type2 == Pers) && vars[V_ADDRESSBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book configuration"));
+
+ return(retval);
+ }
+
+ if((type1 == Glob || type2 == Glob) && vars[V_GLOB_ADDRBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));
+
+ return(retval);
+ }
+
+ /*
+ * There are two cases. If the shuffle is two address books within the
+ * same variable, then they just swap places. If it is a shuffle of an
+ * addrbook from one side of the boundary to the other, just that one
+ * is moved.
+ */
+ if((type1 == Glob && type2 == Glob) ||
+ (type1 == Pers && type2 == Pers)){
+ int how_many_in_list, varnum;
+ int anum1_rel, anum2_rel; /* position in specific list */
+ char **list, **new_list;
+ PerAddrBook tmppab;
+
+ *slide = (anum1 < anum2) ? LINES_PER_ABOOK : -1 * LINES_PER_ABOOK;
+
+ if(type1 == Pers){
+ how_many_in_list = as.how_many_personals;
+ list = VAR_ADDRESSBOOK;
+ varnum = V_ADDRESSBOOK;
+ anum1_rel = anum1;
+ anum2_rel = anum2;
+ }
+ else{
+ how_many_in_list = as.n_addrbk - as.how_many_personals;
+ list = VAR_GLOB_ADDRBOOK;
+ varnum = V_GLOB_ADDRBOOK;
+ anum1_rel = anum1 - as.how_many_personals;
+ anum2_rel = anum2 - as.how_many_personals;
+ }
+
+ /* allocate for new list, same size as old list */
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+
+ /* fill in new_list */
+ for(i = 0; i < how_many_in_list; i++){
+ /* swap anum1 and anum2 */
+ if(i == anum1_rel)
+ j = anum2_rel;
+ else if(i == anum2_rel)
+ j = anum1_rel;
+ else
+ j = i;
+
+ new_list[i] = cpystr(list[j]);
+ }
+
+ new_list[i] = NULL;
+
+ if(set_variable_list(varnum, new_list, TRUE, ew)){
+ if(msg)
+ *msg = cpystr(cancel_msg);
+
+ /* restore old values */
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+ return(retval);
+ }
+
+ retval = 0;
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+
+ /* Swap PerAddrBook structs */
+ tmppab = as.adrbks[anum1];
+ as.adrbks[anum1] = as.adrbks[anum2];
+ as.adrbks[anum2] = tmppab;
+ }
+ else if((type1 == Pers && type2 == Glob) ||
+ (type1 == Glob && type2 == Pers)){
+ int how_many_in_srclist, how_many_in_dstlist;
+ int srcvarnum, dstvarnum, srcanum;
+ int cnt, warn_about_revert = 0;
+ char **t;
+ char **new_src, **new_dst, **srclist, **dstlist;
+ char tmp[200];
+ enum {NotSet, Modify, RevertToDefault, OverRideDefault} modify_config;
+
+ /*
+ * how_many_in_srclist = # in orig src list (Pers or Glob list).
+ * how_many_in_dstlist = # in orig dst list
+ * srcanum = # of highlighted addrbook that is being shuffled
+ */
+ if(type1 == Pers){
+ how_many_in_srclist = as.how_many_personals;
+ how_many_in_dstlist = as.n_addrbk - as.how_many_personals;
+ srclist = VAR_ADDRESSBOOK;
+ dstlist = VAR_GLOB_ADDRBOOK;
+ srcvarnum = V_ADDRESSBOOK;
+ dstvarnum = V_GLOB_ADDRBOOK;
+ srcanum = as.how_many_personals - 1;
+ *slide = (how_many_in_srclist == 1)
+ ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
+ : XTRA_LINES_BETWEEN;
+ }
+ else{
+ how_many_in_srclist = as.n_addrbk - as.how_many_personals;
+ how_many_in_dstlist = as.how_many_personals;
+ srclist = VAR_GLOB_ADDRBOOK;
+ dstlist = VAR_ADDRESSBOOK;
+ srcvarnum = V_GLOB_ADDRBOOK;
+ dstvarnum = V_ADDRESSBOOK;
+ srcanum = as.how_many_personals;
+ *slide = (how_many_in_dstlist == 0)
+ ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
+ : XTRA_LINES_BETWEEN;
+ *slide = -1 * (*slide);
+ }
+
+
+ modify_config = Modify;
+ if(how_many_in_srclist == 1){
+ /*
+ * Deal with reverting to default values of the address book
+ * variables, or with user deleting a default value.
+ */
+ modify_config = NotSet;
+
+ /*
+ * Count how many address books are in the user's config.
+ * This has to be one or zero, because how_many_in_srclist == 1.
+ */
+ cnt = 0;
+ lval = LVAL(&vars[srcvarnum], ew);
+ if(lval && lval[0])
+ for(t = lval; *t != NULL; t++)
+ cnt++;
+
+ /*
+ * We'll revert to the default if it exists, and warn
+ * the user about that.
+ */
+ if(cnt == 1){
+ modify_config = RevertToDefault;
+ /* see if there's a default to revert to */
+ cnt = 0;
+ if(vars[srcvarnum].global_val.l &&
+ vars[srcvarnum].global_val.l[0])
+ for(t = vars[srcvarnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ warn_about_revert = cnt;
+ if(warn_about_revert > 1 && type1 == Pers)
+ *slide = LINES_PER_ABOOK * warn_about_revert +
+ XTRA_LINES_BETWEEN;
+ }
+ /*
+ * User is already using the default.
+ */
+ else if(cnt == 0){
+ modify_config = OverRideDefault;
+ }
+ }
+
+ /*
+ * We're adding one to the dstlist, so need how_many + 1 + 1.
+ */
+ new_dst = (char **)fs_get((how_many_in_dstlist + 2) * sizeof(char *));
+ j = 0;
+
+ /*
+ * Because the Personal list comes before the Global list, when
+ * we move to Global we're inserting a new first element into
+ * the global list (the dstlist).
+ *
+ * When we move from Global to Personal, we're appending a new
+ * last element onto the personal list (the dstlist).
+ */
+ if(type2 == Glob)
+ new_dst[j++] = cpystr(srclist[how_many_in_srclist-1]);
+
+ for(i = 0; i < how_many_in_dstlist; i++)
+ new_dst[j++] = cpystr(dstlist[i]);
+
+ if(type2 == Pers)
+ new_dst[j++] = cpystr(srclist[0]);
+
+ new_dst[j] = NULL;
+
+ /*
+ * The srclist is complicated by the reverting to default
+ * behaviors.
+ */
+ if(modify_config == Modify){
+ /*
+ * In this case we're just removing one from the srclist
+ * so the new_src is of size how_many -1 +1.
+ */
+ new_src = (char **)fs_get((how_many_in_srclist) * sizeof(char *));
+ j = 0;
+
+ for(i = 0; i < how_many_in_srclist-1; i++)
+ new_src[j++] = cpystr(srclist[i + ((type1 == Glob) ? 1 : 0)]);
+
+ new_src[j] = NULL;
+ }
+ else if(modify_config == OverRideDefault){
+ /*
+ * We were using default and will now revert to nothing.
+ */
+ new_src = (char **)fs_get(2 * sizeof(char *));
+ new_src[0] = cpystr("");
+ new_src[1] = NULL;
+ }
+ else if(modify_config == RevertToDefault){
+ /*
+ * We are moving our last user variable out and reverting
+ * to the default value for this variable.
+ */
+ new_src = NULL;
+ }
+
+ if(set_variable_list(dstvarnum, new_dst, TRUE, ew) ||
+ set_variable_list(srcvarnum, new_src, TRUE, ew)){
+ if(msg)
+ *msg = cpystr(cancel_msg);
+
+ /* restore old values */
+ set_current_val(&vars[dstvarnum], TRUE, FALSE);
+ set_current_val(&vars[srcvarnum], TRUE, FALSE);
+ free_list_array(&new_dst);
+ free_list_array(&new_src);
+ return(retval);
+ }
+
+ set_current_val(&vars[dstvarnum], TRUE, FALSE);
+ set_current_val(&vars[srcvarnum], TRUE, FALSE);
+ free_list_array(&new_dst);
+ free_list_array(&new_src);
+
+ retval = (type1 == Pers && warn_about_revert)
+ ? (warn_about_revert + 1) : (srcanum + 1);
+
+ /*
+ * This is a tough case. We're adding one or more new address books
+ * in this case so we need to reset the addrbooks and start over.
+ * We return the number of the address book we just moved after the
+ * reset so that the caller can focus attention on the moved one.
+ * Actually, we return 1+the number so that we can tell it apart
+ * from a return of zero, which just means everything is ok.
+ */
+ if(warn_about_revert){
+ snprintf(tmp, sizeof(tmp),
+ "This address book now %s, reverting to default %s address %s",
+ (type1 == Glob) ? "Personal" : "Global",
+ (type1 == Glob) ? "Global" : "Personal",
+ warn_about_revert > 1 ? "books" : "book");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(msg)
+ *msg = cpystr(tmp);
+ }
+ else{
+ /*
+ * Modify PerAddrBook struct and adjust boundary.
+ * In this case we aren't swapping two addrbooks, but just modifying
+ * one from being global to personal or the reverse. It will
+ * still be the same element in the as.adrbks array.
+ */
+ pab = &as.adrbks[srcanum];
+ if(type2 == Glob){
+ as.how_many_personals--;
+ pab->type |= GLOBAL;
+ if(pab->access != NoAccess)
+ pab->access = ReadOnly;
+ }
+ else{
+ as.how_many_personals++;
+ pab->type &= ~GLOBAL;
+ if(pab->access != NoAccess && pab->access != MaybeRorW)
+ pab->access = ReadWrite;
+ }
+
+ snprintf(tmp, sizeof(tmp),
+ "This address book now %s",
+ (type1 == Glob) ? "Personal" : "Global");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(msg)
+ *msg = cpystr(tmp);
+ }
+ }
+
+ return(retval);
+}
+
+
+int
+ab_compose_to_addr(long int cur_line, int agg, int allow_role)
+{
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ SAVE_STATE_S state;
+ BuildTo bldto;
+
+ dprint((2, "- ab_compose_to_addr -\n"));
+
+ save_state(&state);
+
+ bldto.type = Str;
+ bldto.arg.str = NULL;
+
+ if(agg){
+ int i;
+ size_t incr = 100, avail, alloced;
+ char *to = NULL;
+
+ to = (char *)fs_get(incr);
+ *to = '\0';
+ avail = incr;
+ alloced = incr;
+
+ /*
+ * Run through all of the selected entries
+ * in all of the address books.
+ * Put the nicknames together into one long
+ * string with comma separators.
+ */
+ for(i = 0; i < as.n_addrbk; i++){
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+ char *a_string;
+ AddrScrn_Disp fake_dl;
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+
+ /*
+ * Since we're picking up address book entries
+ * directly from the address books and have
+ * no knowledge of the display lines they came
+ * from, we don't know the dl's that go with
+ * them. We need to pass a dl to abe_to_nick
+ * but it really is only going to use the
+ * type in this case.
+ */
+ dl = &fake_dl;
+ dl->type = (abe->tag == Single) ? Simple : ListHead;
+ a_string = abe_to_nick_or_addr_string(abe, dl, i);
+
+ while(abe && avail < (size_t)strlen(a_string)+1){
+ alloced += incr;
+ avail += incr;
+ fs_resize((void **)&to, alloced);
+ }
+
+ if(!*to){
+ strncpy(to, a_string, alloced);
+ to[alloced-1] = '\0';
+ }
+ else{
+ strncat(to, ",", alloced-strlen(to)-1);
+ to[alloced-1] = '\0';
+ strncat(to, a_string, alloced-strlen(to)-1);
+ to[alloced-1] = '\0';
+ }
+
+ avail -= (strlen(a_string) + 1);
+ fs_give((void **)&a_string);
+ }
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = to;
+ }
+ else{
+ if(is_addr(cur_line)){
+
+ dl = dlist(cur_line);
+ abe = ae(cur_line);
+
+ if(dl->type == ListEnt){
+ bldto.type = Str;
+ bldto.arg.str = cpystr(listmem(cur_line));
+ }
+ else{
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ }
+ }
+ }
+
+ if(bldto.type == Str && bldto.arg.str == NULL)
+ bldto.arg.str = cpystr("");
+
+ ab_compose_internal(bldto, allow_role);
+
+ restore_state(&state);
+
+ if(bldto.type == Str && bldto.arg.str)
+ fs_give((void **)&bldto.arg.str);
+
+ /*
+ * Window size may have changed in composer.
+ * Pine_send will have reset the window size correctly,
+ * but we still have to reset our address book data structures.
+ */
+ ab_resize();
+ ps_global->mangled_screen = 1;
+ return(1);
+}
+
+
+/*
+ * Used by the two compose routines.
+ */
+void
+ab_compose_internal(BuildTo bldto, int allow_role)
+{
+ int good_addr;
+ char *addr, *fcc, *error = NULL;
+ ACTION_S *role = NULL;
+ void (*prev_screen)(struct pine *) = ps_global->prev_screen,
+ (*redraw)(void) = ps_global->redrawer;
+
+ if(allow_role)
+ ps_global->redrawer = NULL;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ fcc = NULL;
+ addr = NULL;
+
+ good_addr = (our_build_address(bldto, &addr, &error, &fcc, NULL) >= 0);
+
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ if(!good_addr && addr && *addr)
+ fs_give((void **)&addr); /* relying on fs_give setting addr to NULL */
+
+ if(allow_role){
+ /* Setup role */
+ if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
+ cmd_cancelled("Composition");
+ ps_global->next_screen = prev_screen;
+ ps_global->redrawer = redraw;
+ return;
+ }
+
+ /*
+ * If default role was selected (NULL) we need to make up a role which
+ * won't do anything, but will cause compose_mail to think there's
+ * already a role so that it won't try to confirm the default.
+ */
+ if(role)
+ role = copy_action(role);
+ else{
+ role = (ACTION_S *)fs_get(sizeof(*role));
+ memset((void *)role, 0, sizeof(*role));
+ role->nick = cpystr("Default Role");
+ }
+ }
+
+ compose_mail(addr, fcc, role, NULL, NULL);
+
+ if(addr)
+ fs_give((void **)&addr);
+
+ if(fcc)
+ fs_give((void **)&fcc);
+}
+
+
+/*
+ * Export addresses into a file.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ *
+ * Returns -- 1 if the export is done
+ * 0 if not
+ */
+int
+ab_export(struct pine *ps, long int cur_line, int command_line, int agg)
+{
+ int ret = 0, i, retflags = GER_NONE;
+ int r, orig_errno, failure = 0;
+ struct variable *vars = ps->vars;
+ char filename[MAXPATH+1], full_filename[MAXPATH+1];
+ STORE_S *store;
+ gf_io_t pc;
+ long start_of_append;
+ char *addr = NULL, *error = NULL;
+ BuildTo bldto;
+ char *p;
+ int good_addr, plur, vcard = 0, tab = 0;
+ static HISTORY_S *history = NULL;
+ AdrBk_Entry *abe;
+ VCARD_INFO_S *vinfo;
+ static ESCKEY_S ab_export_opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+ static ESCKEY_S vcard_or_addresses[] = {
+ {'a', 'a', "A", N_("Address List")},
+ {'v', 'v', "V", N_("VCard")},
+ /* TRANSLATORS: TabSep is a command key label meaning Tab Separated List */
+ {'t', 't', "T", N_("TabSep")},
+ {-1, 0, NULL, NULL}};
+
+
+ dprint((2, "- ab_export -\n"));
+
+ if(ps->restricted){
+ q_status_message(SM_ORDER, 0, 3,
+ "Alpine demo can't export addresses to files");
+ return(ret);
+ }
+
+ while(1){
+ i = radio_buttons(_("Export list of addresses, vCard format, or Tab Separated ? "),
+ command_line, vcard_or_addresses, 'a', 'x',
+ NO_HELP, RB_NORM|RB_RET_HELP);
+ if(i == 3){
+ /* TRANSLATORS: a screen title */
+ helper(h_ab_export_vcard, _("HELP FOR EXPORT FORMAT"),
+ HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ break;
+ }
+
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ return(ret);
+
+ case 'a':
+ break;
+
+ case 'v':
+ vcard++;
+ break;
+
+ case 't':
+ tab++;
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_export");
+ return(ret);
+ }
+
+ if(agg)
+ plur = 1;
+ else{
+ abe = ae(cur_line);
+ plur = (abe && abe->tag == List);
+ }
+
+ filename[0] = '\0';
+ r = 0;
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ ab_export_opts[++r].ch = ctrl('I');
+ ab_export_opts[r].rval = 11;
+ ab_export_opts[r].name = "TAB";
+ ab_export_opts[r].label = N_("Complete");
+ }
+
+ ab_export_opts[++r].ch = -1;
+
+ r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
+ plur ? _("addresses") : _("address"),
+ _("EXPORT"), ab_export_opts,
+ &retflags, command_line, GE_IS_EXPORT, &history);
+
+ if(r < 0){
+ switch(r){
+ case -1:
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"), VAR_OPER_DIR);
+ break;
+ }
+
+ goto fini;
+ }
+
+ dprint((5, "Opening file \"%s\" for export\n",
+ full_filename ? full_filename : "?"));
+
+ if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error opening file \"%s\" for address export: %s"),
+ full_filename, error_description(errno));
+ goto fini;
+ }
+
+ /*
+ * The write_single_vcard_entry function wants a pc.
+ */
+ if(vcard || tab)
+ gf_set_so_writec(&pc, store);
+
+ start_of_append = so_tell(store);
+
+ if(agg){
+ for(i = 0; !failure && i < as.n_addrbk; i++){
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while(!failure && (num = entry_get_next(&next_one)) != NO_NEXT){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if((vcard || tab) && abe){
+ /*
+ * There is no place to store the charset information
+ * so we don't ask for it.
+ */
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
+ failure++;
+ else{
+ if(vcard)
+ write_single_vcard_entry(ps, pc, vinfo);
+ else
+ write_single_tab_entry(pc, vinfo);
+
+ free_vcard_info(&vinfo);
+ }
+ }
+ else if(abe){
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ error = NULL;
+ addr = NULL;
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+
+ if(error){
+ q_status_message1(SM_ORDER, 0, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ /* rfc1522_decode the addr */
+ if(addr){
+ size_t len;
+
+ len = 4*strlen(addr)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
+ fs_give((void **)&addr);
+ addr = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+
+ if(good_addr){
+ int quoted = 0;
+
+ /*
+ * Change the unquoted commas into newlines.
+ * Not worth it to do complicated quoting,
+ * just consider double quotes.
+ */
+ for(p = addr; *p; p++){
+ if(*p == '"')
+ quoted = !quoted;
+ else if(!quoted && *p == ','){
+ *p++ = '\n';
+ removing_leading_white_space(p);
+ p--;
+ }
+ }
+
+ if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
+ orig_errno = errno;
+ failure = 1;
+ }
+ }
+
+ if(addr)
+ fs_give((void **)&addr);
+ }
+ }
+ }
+ }
+ else{
+ AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+ abe = ae(cur_line);
+ if((vcard || tab) && abe){
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
+ failure++;
+ else{
+ if(vcard)
+ write_single_vcard_entry(ps, pc, vinfo);
+ else
+ write_single_tab_entry(pc, vinfo);
+
+ free_vcard_info(&vinfo);
+ }
+ }
+ else{
+
+ if(dl->type == ListHead && listmem_count_from_abe(abe) == 0){
+ error = _("List is empty, nothing to export!");
+ good_addr = 0;
+ }
+ else if(dl->type == ListEnt){
+ bldto.type = Str;
+ bldto.arg.str = listmem(cur_line);
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+ }
+ else{
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+ }
+
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ /* Have to rfc1522_decode the addr */
+ if(addr){
+ size_t len;
+ len = 4*strlen(addr)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
+ fs_give((void **)&addr);
+ addr = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+
+ if(good_addr){
+ int quoted = 0;
+
+ /*
+ * Change the unquoted commas into newlines.
+ * Not worth it to do complicated quoting,
+ * just consider double quotes.
+ */
+ for(p = addr; *p; p++){
+ if(*p == '"')
+ quoted = !quoted;
+ else if(!quoted && *p == ','){
+ *p++ = '\n';
+ removing_leading_white_space(p);
+ p--;
+ }
+ }
+
+ if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
+ orig_errno = errno;
+ failure = 1;
+ }
+ }
+
+ if(addr)
+ fs_give((void **)&addr);
+ }
+ }
+
+ if(vcard || tab)
+ gf_clear_so_writec(store);
+
+ if(so_give(&store)) /* release storage */
+ failure++;
+
+ if(failure){
+ our_truncate(full_filename, (off_t)start_of_append);
+ dprint((1, "FAILED Export: file \"%s\" : %s\n",
+ full_filename ? full_filename : "?",
+ error_description(orig_errno)));
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error exporting to \"%s\" : %s"),
+ filename, error_description(orig_errno));
+ }
+ else{
+ ret = 1;
+ q_status_message3(SM_ORDER,0,3,
+ "%s %s to file \"%s\"",
+ (vcard || tab) ? (agg ? "Entries" : "Entry")
+ : (plur ? "Addresses" : "Address"),
+ retflags & GER_OVER
+ ? "overwritten"
+ : retflags & GER_APPEND ? "appended" : "exported",
+ filename);
+ }
+
+fini:
+ ps->mangled_footer = 1;
+ return(ret);
+}
+
+
+/*
+ * Forward an address book entry or entries via email attachment.
+ *
+ * We use the vCard standard to send entries. We group multiple entries
+ * using the BEGIN/END construct of vCard, not with multiple MIME parts.
+ * A limitation of vCard is that there can be only one charset for the
+ * whole group we send, so we might lose that information.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ */
+int
+ab_forward(struct pine *ps, long int cur_line, int agg)
+{
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ ENVELOPE *outgoing = NULL;
+ BODY *pb, *body = NULL;
+ PART **pp;
+ char *sig;
+ gf_io_t pc;
+ int i, ret = 0;
+ VCARD_INFO_S *vinfo;
+ ACTION_S *role = NULL;
+
+ dprint((2, "- ab_forward -\n"));
+
+ if(!agg){
+ dl = dlist(cur_line);
+ if(dl->type != ListHead && dl->type != Simple)
+ return(ret);
+
+ abe = ae(cur_line);
+ if(!abe){
+ q_status_message(SM_ORDER, 3, 3, _("Trouble accessing current entry"));
+ return(ret);
+ }
+ }
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+ if(agg && as.selections > 1)
+ outgoing->subject = cpystr("Forwarded address book entries from Alpine");
+ else
+ outgoing->subject = cpystr("Forwarded address book entry from Alpine");
+
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+ /*---- The TEXT part/body ----*/
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ /*--- Allocate an object for the body ---*/
+ if((body->nested.part->body.contents.text.data =
+ (void *)so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int did_sig = 0;
+ long rflags = ROLE_COMPOSE;
+ PAT_STATE dummy;
+
+ pp = &(body->nested.part->next);
+
+ if(nonempty_patterns(rflags, &dummy)){
+ /*
+ * This is really more like Compose, even though it
+ * is called Forward.
+ */
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel */
+ role = NULL;
+ cmd_cancelled("Composition");
+ goto bomb;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
+ role->nick);
+
+ if((sig = detoken(role, NULL, 2, 0, 1, NULL, NULL)) != NULL){
+ if(*sig){
+ so_puts((STORE_S *)body->nested.part->body.contents.text.data,
+ sig);
+ did_sig++;
+ }
+
+ fs_give((void **)&sig);
+ }
+
+ /* so we don't have an empty part */
+ if(!did_sig)
+ so_puts((STORE_S *)body->nested.part->body.contents.text.data, "\n");
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text"));
+ goto bomb;
+ }
+
+
+ /*---- create the attachment, and write abook entry into it ----*/
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pb->type = TYPETEXT;
+ pb->encoding = ENCOTHER; /* let data decide */
+ pb->id = generate_message_id();
+ pb->subtype = cpystr("DIRECTORY");
+ if(agg && as.selections > 1)
+ pb->description = cpystr("Alpine addressbook entries");
+ else
+ pb->description = cpystr("Alpine addressbook entry");
+
+ pb->parameter = NULL;
+ set_parameter(&pb->parameter, "profile", "vCard");
+
+ if((pb->contents.text.data = (void *)so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ int are_some_unqualified = 0, expand_nicks = 0;
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ gf_set_so_writec(&pc, (STORE_S *) pb->contents.text.data);
+
+ if(agg){
+ for(i = 0; i < as.n_addrbk && !are_some_unqualified; i++){
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT &&
+ !are_some_unqualified){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ else{
+ /*
+ * Search through the addresses to see if there are any
+ * that are unqualified, and so would be different if
+ * expanded.
+ */
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+ }
+
+ if(are_some_unqualified){
+ switch(want_to(_("Expand nicknames"), 'y', 'x', h_ab_forward,WT_NORM)){
+ case 'x':
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
+ goto bomb;
+
+ case 'y':
+ expand_nicks = 1;
+ break;
+
+ case 'n':
+ expand_nicks = 0;
+ break;
+ }
+
+ ps->mangled_footer = 1;
+ }
+
+ if(agg){
+ for(i = 0; i < as.n_addrbk; i++){
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ goto bomb;
+ }
+ else{
+ write_single_vcard_entry(ps, pc, vinfo);
+ free_vcard_info(&vinfo);
+ }
+ }
+ }
+ }
+ else{
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ goto bomb;
+ }
+ else{
+ write_single_vcard_entry(ps, pc, vinfo);
+ free_vcard_info(&vinfo);
+ }
+ }
+
+ /* This sets parameter charset, if necessary, and encoding */
+ set_mime_type_by_grope(pb);
+ set_charset_possibly_to_ascii(pb, "UTF-8");
+ pb->size.bytes =
+ strlen((char *)so_text((STORE_S *)pb->contents.text.data));
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text"));
+ goto bomb;
+ }
+
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+
+ pine_send(outgoing, &body, _("FORWARDING ADDRESS BOOK ENTRY"), role, NULL,
+ NULL, NULL, NULL, NULL, 0);
+
+ ps->mangled_screen = 1;
+ ret = 1;
+
+bomb:
+ if(outgoing)
+ mail_free_envelope(&outgoing);
+
+ if(body)
+ pine_free_body(&body);
+
+ free_action(&role);
+ return(ret);
+}
+
+
+/*
+ * Given an Adrbk_Entry fill in some of the fields in a VCARD_INFO_S
+ * for use by write_single_vcard_entry. The returned structure is freed
+ * by the caller.
+ */
+VCARD_INFO_S *
+prepare_abe_for_vcard(struct pine *ps, AdrBk_Entry *abe, int expand_nicks)
+{
+ VCARD_INFO_S *vinfo = NULL;
+ char *init_addr = NULL, *addr = NULL, *astring;
+ int cnt;
+ ADDRESS *adrlist = NULL;
+
+ if(!abe)
+ return(vinfo);
+
+ vinfo = (VCARD_INFO_S *) fs_get(sizeof(*vinfo));
+ memset((void *) vinfo, 0, sizeof(*vinfo));
+
+ if(abe->nickname && abe->nickname[0]){
+ vinfo->nickname = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->nickname[0] = cpystr(abe->nickname);
+ vinfo->nickname[1] = NULL;
+ }
+
+ if(abe->fcc && abe->fcc[0]){
+ vinfo->fcc = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->fcc[0] = cpystr(abe->fcc);
+ vinfo->fcc[1] = NULL;
+ }
+
+ if(abe->extra && abe->extra[0]){
+ vinfo->note = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->note[0] = cpystr(abe->extra);
+ vinfo->note[1] = NULL;
+ }
+
+ if(abe->fullname && abe->fullname[0]){
+ char *fn, *last = NULL, *middle = NULL, *first = NULL;
+
+ fn = adrbk_formatname(abe->fullname, &first, &last);
+ if(fn){
+ if(*fn){
+ vinfo->fullname = (char **)fs_get((1+1) * sizeof(char *));
+ vinfo->fullname[0] = fn;
+ vinfo->fullname[1] = NULL;
+ }
+ else
+ fs_give((void **)&fn);
+ }
+
+ if(last && *last){
+ if(first && (middle=strindex(first, ' '))){
+ *middle++ = '\0';
+ middle = skip_white_space(middle);
+ }
+
+ vinfo->last = last;
+ vinfo->first = first;
+ vinfo->middle = middle ? cpystr(middle) : NULL;
+ first = NULL;
+ last = NULL;
+ }
+
+ if(last)
+ fs_give((void **)&last);
+ if(first)
+ fs_give((void **)&first);
+ }
+
+ /* expand nicknames and fully-qualify unqualified names */
+ if(expand_nicks){
+ char *error = NULL;
+ BuildTo bldto;
+ ADDRESS *a;
+
+ if(abe->tag == Single)
+ init_addr = cpystr(abe->addr.addr);
+ else{
+ char **ll;
+ char *p;
+ long length;
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(ll = abe->addr.list; ll && *ll; ll++)
+ length += (strlen(*ll) + 2);
+
+ if(length)
+ length -= 2L;
+
+ init_addr = (char *)fs_get((size_t)(length+1L) * sizeof(char));
+ p = init_addr;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ sstrncpy(&p, *ll, length-(p-init_addr));
+ if(*(ll+1))
+ sstrncpy(&p, ", ", length-(p-init_addr));
+ }
+
+ init_addr[length] = '\0';
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = init_addr;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ free_vcard_info(&vinfo);
+ return(NULL);
+ }
+
+ if(addr)
+ rfc822_parse_adrlist(&adrlist, addr, ps->maildomain);
+
+ for(cnt = 0, a = adrlist; a; a = a->next)
+ cnt++;
+
+ vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));
+
+ for(cnt = 0, a = adrlist; a; a = a->next){
+ char *bufp;
+ ADDRESS *next_addr;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ astring = addr_string(a, bufp, len);
+ a->next = next_addr;
+ vinfo->email[cnt++] = cpystr(astring ? astring : "");
+ fs_give((void **)&bufp);
+ }
+
+ vinfo->email[cnt] = '\0';
+ }
+ else{ /* don't expand or qualify */
+ if(abe->tag == Single){
+ astring =
+ (abe->addr.addr && abe->addr.addr[0]) ? abe->addr.addr : "";
+ vinfo->email = (char **)fs_get((1+1) * sizeof(char *));
+ vinfo->email[0] = cpystr(astring);
+ vinfo->email[1] = '\0';
+ }
+ else{
+ char **ll;
+
+ for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
+ cnt++;
+
+ vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));
+ for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
+ vinfo->email[cnt++] = cpystr(*ll);
+
+ vinfo->email[cnt] = '\0';
+ }
+ }
+
+ return(vinfo);
+}
+
+
+void
+free_vcard_info(VCARD_INFO_S **vinfo)
+{
+ if(vinfo && *vinfo){
+ if((*vinfo)->nickname)
+ free_list_array(&(*vinfo)->nickname);
+ if((*vinfo)->fullname)
+ free_list_array(&(*vinfo)->fullname);
+ if((*vinfo)->fcc)
+ free_list_array(&(*vinfo)->fcc);
+ if((*vinfo)->note)
+ free_list_array(&(*vinfo)->note);
+ if((*vinfo)->title)
+ free_list_array(&(*vinfo)->title);
+ if((*vinfo)->tel)
+ free_list_array(&(*vinfo)->tel);
+ if((*vinfo)->email)
+ free_list_array(&(*vinfo)->email);
+
+ if((*vinfo)->first)
+ fs_give((void **)&(*vinfo)->first);
+ if((*vinfo)->middle)
+ fs_give((void **)&(*vinfo)->middle);
+ if((*vinfo)->last)
+ fs_give((void **)&(*vinfo)->last);
+
+ fs_give((void **)vinfo);
+ }
+}
+
+
+/*
+ *
+ */
+void
+write_single_vcard_entry(struct pine *ps, gf_io_t pc, VCARD_INFO_S *vinfo)
+{
+ char *decoded, *tmp2, *tmp = NULL, *hdr;
+ char **ll;
+ int i, did_fn = 0, did_n = 0;
+ int cr;
+ char eol[3];
+#define FOLD_BY 75
+
+ if(!vinfo)
+ return;
+
+#if defined(DOS) || defined(OS2)
+ cr = 1;
+#else
+ cr = 0;
+#endif
+
+ if(cr)
+ strncpy(eol, "\r\n", sizeof(eol));
+ else
+ strncpy(eol, "\n", sizeof(eol));
+
+ eol[sizeof(eol)-1] = '\0';
+
+ gf_puts("BEGIN:VCARD", pc);
+ gf_puts(eol, pc);
+ gf_puts("VERSION:3.0", pc);
+ gf_puts(eol, pc);
+
+ for(i = 0; i < 7; i++){
+ switch(i){
+ case 0:
+ ll = vinfo->nickname;
+ hdr = "NICKNAME:";
+ break;
+
+ case 1:
+ ll = vinfo->fullname;
+ hdr = "FN:";
+ break;
+
+ case 2:
+ ll = vinfo->email;
+ hdr = "EMAIL:";
+ break;
+
+ case 3:
+ ll = vinfo->title;
+ hdr = "TITLE:";
+ break;
+
+ case 4:
+ ll = vinfo->note;
+ hdr = "NOTE:";
+ break;
+
+ case 5:
+ ll = vinfo->fcc;
+ hdr = "X-FCC:";
+ break;
+
+ case 6:
+ ll = vinfo->tel;
+ hdr = "TEL:";
+ break;
+
+ default:
+ panic("can't happen in write_single_vcard_entry");
+ }
+
+ for(; ll && *ll; ll++){
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);
+
+ tmp = vcard_escape(decoded);
+ if(tmp){
+ if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, hdr, " ", FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ if(i == 1)
+ did_fn++;
+ }
+
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ if(vinfo->last && vinfo->last[0]){
+ char *pl, *pf, *pm;
+
+ pl = vcard_escape(vinfo->last);
+ pf = (vinfo->first && *vinfo->first) ? vcard_escape(vinfo->first)
+ : NULL;
+ pm = (vinfo->middle && *vinfo->middle) ? vcard_escape(vinfo->middle)
+ : NULL;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
+ (pl && *pl) ? pl : "",
+ ((pf && *pf) || (pm && *pm)) ? ";" : "",
+ (pf && *pf) ? pf : "",
+ (pm && *pm) ? ";" : "",
+ (pm && *pm) ? pm : "");
+
+ if((tmp2 = fold(tmp_20k_buf, FOLD_BY, FOLD_BY, "N:", " ",
+ FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ did_n++;
+ }
+
+ if(pl)
+ fs_give((void **)&pl);
+ if(pf)
+ fs_give((void **)&pf);
+ if(pm)
+ fs_give((void **)&pm);
+ }
+
+ /*
+ * These two types are required in draft-ietf-asid-mime-vcard-06, which
+ * is April 98 and is in last call.
+ */
+ if(!did_fn || !did_n){
+ if(did_n){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
+ (vinfo->first && *vinfo->first) ? vinfo->first : "",
+ (vinfo->first && *vinfo->first &&
+ vinfo->middle && *vinfo->middle) ? " " : "",
+ (vinfo->middle && *vinfo->middle) ? vinfo->middle : "",
+ (((vinfo->first && *vinfo->first) ||
+ (vinfo->middle && *vinfo->middle)) &&
+ vinfo->last && *vinfo->last) ? " " : "",
+ (vinfo->last && *vinfo->last) ? vinfo->last : "");
+
+ tmp = vcard_escape(tmp_20k_buf);
+ if(tmp){
+ if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, "FN:", " ",
+ FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ did_n++;
+ }
+
+ fs_give((void **)&tmp);
+ }
+ }
+ else{
+ if(!did_fn){
+ gf_puts("FN:<Unknown>", pc);
+ gf_puts(eol, pc);
+ }
+
+ gf_puts("N:<Unknown>", pc);
+ gf_puts(eol, pc);
+ }
+ }
+
+ gf_puts("END:VCARD", pc);
+ gf_puts(eol, pc);
+}
+
+
+/*
+ *
+ */
+void
+write_single_tab_entry(gf_io_t pc, VCARD_INFO_S *vinfo)
+{
+ char *decoded, *tmp = NULL;
+ char **ll;
+ int i, first;
+ char *eol;
+
+ if(!vinfo)
+ return;
+
+#if defined(DOS) || defined(OS2)
+ eol = "\r\n";
+#else
+ eol = "\n";
+#endif
+
+ for(i = 0; i < 4; i++){
+ switch(i){
+ case 0:
+ ll = vinfo->nickname;
+ break;
+
+ case 1:
+ ll = vinfo->fullname;
+ break;
+
+ case 2:
+ ll = vinfo->email;
+ break;
+
+ case 3:
+ ll = vinfo->note;
+ break;
+
+ default:
+ panic("can't happen in write_single_tab_entry");
+ }
+
+ if(i)
+ gf_puts("\t", pc);
+
+ for(first = 1; ll && *ll; ll++){
+
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);
+ tmp = vcard_escape(decoded);
+ if(tmp){
+ if(i == 2 && !first)
+ gf_puts(",", pc);
+ else
+ first = 0;
+
+ gf_puts(tmp, pc);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ gf_puts(eol, pc);
+}
+
+
+/*
+ * for ab_save percent done
+ */
+static int total_to_copy;
+static int copied_so_far;
+int
+percent_done_copying(void)
+{
+ return((copied_so_far * 100) / total_to_copy);
+}
+
+int
+cmp_action_list(const qsort_t *a1, const qsort_t *a2)
+{
+ ACTION_LIST_S *x = (ACTION_LIST_S *)a1;
+ ACTION_LIST_S *y = (ACTION_LIST_S *)a2;
+
+ if(x->pab != y->pab)
+ return((x->pab > y->pab) ? 1 : -1); /* order doesn't matter */
+
+ /*
+ * The only one that matters is when both x and y have dup lit.
+ * For the others, just need to be consistent so sort will terminate.
+ */
+ if(x->dup){
+ if(y->dup)
+ return((x->num_in_dst > y->num_in_dst) ? -1
+ : (x->num_in_dst == y->num_in_dst) ? 0 : 1);
+ else
+ return(-1);
+ }
+ else if(y->dup)
+ return(1);
+ else
+ return((x->num > y->num) ? -1 : (x->num == y->num) ? 0 : 1);
+}
+
+
+/*
+ * Copy a bunch of address book entries to a particular address book.
+ *
+ * Args abook -- the current addrbook handle
+ * cur_line -- the current line the cursor is on
+ * command_line -- the line to prompt on
+ * agg -- 1 if this is an aggregate copy
+ *
+ * Returns 1 if successful, 0 if not
+ */
+int
+ab_save(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int agg)
+{
+ PerAddrBook *pab_dst, *pab;
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+ int rc, i;
+ int how_many_dups = 0, how_many_to_copy = 0, skip_dups = 0;
+ int how_many_no_action = 0, ret = 1;
+ int err = 0, need_write = 0, we_cancel = 0;
+ int act_list_size, special_case = 0;
+ adrbk_cntr_t num, new_entry_num;
+ char warn[2][MAX_NICKNAME+1];
+ char warning[MAX_NICKNAME+1];
+ char tmp[MAX(200,2*MAX_NICKNAME+80)];
+ ACTION_LIST_S *action_list = NULL, *al;
+ static ESCKEY_S save_or_export[] = {
+ {'s', 's', "S", N_("Save")},
+ {'e', 'e', "E", N_("Export")},
+ {-1, 0, NULL, NULL}};
+
+ if(!agg)
+ snprintf(tmp, sizeof(tmp), _("Save highlighted entry to address book or Export to filesystem ? "));
+ else if(as.selections > 1)
+ snprintf(tmp, sizeof(tmp), _("Save selected entries to address book or Export to filesystem ? "));
+ else if(as.selections == 1)
+ snprintf(tmp, sizeof(tmp), _("Save selected entry to address book or Export to filesystem ? "));
+ else
+ snprintf(tmp, sizeof(tmp), _("Save to address book or Export to filesystem ? "));
+
+ i = radio_buttons(tmp, -FOOTER_ROWS(ps), save_or_export, 's', 'x',
+ h_ab_save_exp, RB_NORM);
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ return(0);
+
+ case 'e':
+ return(ab_export(ps, cur_line, command_line, agg));
+
+ case 's':
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_save");
+ return(0);
+ }
+
+ pab_dst = setup_for_addrbook_add(&state, command_line, _("Save"));
+ if(!pab_dst)
+ goto get_out;
+
+ pab = &as.adrbks[as.cur];
+
+ dprint((2, "- ab_save: %s -> %s (agg=%d)-\n",
+ pab->abnick ? pab->abnick : "?",
+ pab_dst->abnick ? pab_dst->abnick : "?", agg));
+
+ if(agg)
+ act_list_size = as.selections;
+ else
+ act_list_size = 1;
+
+ action_list = (ACTION_LIST_S *)fs_get((act_list_size+1) *
+ sizeof(ACTION_LIST_S));
+ memset((void *)action_list, 0, (act_list_size+1) * sizeof(ACTION_LIST_S));
+ al = action_list;
+
+ if(agg){
+
+ for(i = 0; i < as.n_addrbk; i++){
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+ if(pab != pab_dst &&
+ pab->ostatus != Open &&
+ pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay){
+ q_status_message1(SM_ORDER, 0, 4,
+ _("Can't re-open address book %s to save from"),
+ pab->abnick);
+ err++;
+ goto get_out;
+ }
+
+ set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
+ if(al->skip)
+ how_many_no_action++;
+ else{
+ if(al->dup){
+ if(how_many_dups < 2 && warning[0]){
+ strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
+ warn[how_many_dups][MAX_NICKNAME] = '\0';
+ }
+
+ how_many_dups++;
+ }
+
+ how_many_to_copy++;
+ }
+
+ al++;
+ }
+ }
+ }
+ else{
+ if(is_addr(cur_line)){
+ AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+
+ if(dl->type == ListEnt)
+ special_case++;
+
+ if(pab && dl){
+ num = dl->elnum;
+ set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
+ }
+ else
+ al->skip = 1;
+
+ if(al->skip)
+ how_many_no_action++;
+ else{
+ if(al->dup){
+ if(how_many_dups < 2 && warning[0]){
+ strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
+ warn[how_many_dups][MAX_NICKNAME] = '\0';
+ }
+
+ how_many_dups++;
+ }
+
+ how_many_to_copy++;
+ }
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4, _("No current entry to save"));
+ goto get_out;
+ }
+ }
+
+ if(how_many_to_copy == 0 && how_many_no_action == 1 && act_list_size == 1)
+ special_case++;
+
+ if(special_case){
+ TA_STATE_S tas, *tasp;
+
+ /* Not going to use the action_list now */
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ tasp = &tas;
+ tas.state = state;
+ tas.pab = pab_dst;
+ take_this_one_entry(ps, &tasp, abook, cur_line);
+
+ /*
+ * If take_this_one_entry or its children didn't do this for
+ * us, we do it here.
+ */
+ if(tas.pab)
+ restore_state(&(tas.state));
+
+ /*
+ * We don't have enough information to know what to return.
+ */
+ return(0);
+ }
+
+ /* nothing to do (except for special Take case below) */
+ if(how_many_to_copy == 0){
+ if(how_many_no_action == 0){
+ err++;
+ goto get_out;
+ }
+ else{
+ restore_state(&state);
+
+ if(how_many_no_action > 1)
+ snprintf(tmp, sizeof(tmp), _("Saved %d entries to %s"), how_many_no_action, pab_dst->abnick);
+ else
+ snprintf(tmp, sizeof(tmp), _("Saved %d entry to %s"), how_many_no_action, pab_dst->abnick);
+
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 0, 4, tmp);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ return(ret);
+ }
+ }
+
+ /*
+ * If there are some nicknames which already exist in the selected
+ * abook, ask user what to do.
+ */
+ if(how_many_dups > 0){
+ if(how_many_dups == 1)
+ snprintf(tmp, sizeof(tmp), _("Entry with nickname \"%.*s\" already exists, replace "),
+ MAX_NICKNAME, warn[0]);
+ else if(how_many_dups == 2)
+ snprintf(tmp, sizeof(tmp),
+ _("Nicknames \"%.*s\" and \"%.*s\" already exist, replace "),
+ MAX_NICKNAME, warn[0], MAX_NICKNAME, warn[1]);
+ else
+ snprintf(tmp, sizeof(tmp), _("%d of the nicknames already exist, replace "),
+ how_many_dups);
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ switch(want_to(tmp, 'n', 'x', h_ab_copy_dups, WT_NORM)){
+ case 'n':
+ skip_dups++;
+ if(how_many_to_copy == how_many_dups){
+ restore_state(&state);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ return(ret);
+ }
+
+ break;
+
+ case 'y':
+ break;
+
+ case 'x':
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * Because the deletes happen immediately we have to delete from high
+ * entry number towards lower entry numbers so that we are deleting
+ * the correct entries. In order to do that we'll sort the action_list
+ * to give us a safe order.
+ */
+ if(!skip_dups && how_many_dups > 1)
+ qsort((qsort_t *)action_list, (size_t)as.selections, sizeof(*action_list),
+ cmp_action_list);
+
+ /*
+ * Set up the busy alarm percent counters.
+ */
+ total_to_copy = how_many_to_copy - (skip_dups ? how_many_dups : 0);
+ copied_so_far = 0;
+ we_cancel = busy_cue(_("Saving entries"),
+ (total_to_copy > 4) ? percent_done_copying : NULL, 0);
+
+ /*
+ * Add the list of entries to the destination abook.
+ */
+ for(al = action_list; al && al->pab; al++){
+ AdrBk_Entry *abe;
+
+ if(al->skip || (skip_dups && al->dup))
+ continue;
+
+ if(!(abe = adrbk_get_ae(al->pab->address_book, (a_c_arg_t) al->num))){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error saving entry: %s"),
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+
+ /*
+ * Delete existing dups and replace them.
+ */
+ if(al->dup){
+
+ /* delete the existing entry */
+ rc = 0;
+ if(adrbk_delete(pab_dst->address_book,
+ (a_c_arg_t)al->num_in_dst, 1, 0, 0, 0) == 0){
+ need_write++;
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error replacing entry in %s: %s"),
+ pab_dst->abnick,
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * Now we have a clean slate to work with.
+ * Add (sorted in correctly) or append abe to the destination
+ * address book.
+ */
+ if(total_to_copy <= 1)
+ rc = adrbk_add(pab_dst->address_book,
+ NO_NEXT,
+ abe->nickname,
+ abe->fullname,
+ abe->tag == Single ? abe->addr.addr : NULL,
+ abe->fcc,
+ abe->extra,
+ abe->tag,
+ &new_entry_num,
+ (int *)NULL,
+ 0,
+ 0,
+ 0);
+ else
+ rc = adrbk_append(pab_dst->address_book,
+ abe->nickname,
+ abe->fullname,
+ abe->tag == Single ? abe->addr.addr : NULL,
+ abe->fcc,
+ abe->extra,
+ abe->tag,
+ &new_entry_num);
+
+ if(rc == 0)
+ need_write++;
+
+ /*
+ * If the entry we copied is a list, we also have to add
+ * the list members to the copy.
+ */
+ if(rc == 0 && abe->tag == List){
+ int save_sort_rule;
+
+ /*
+ * We want it to copy the list in the exact order
+ * without sorting it.
+ */
+ save_sort_rule = pab_dst->address_book->sort_rule;
+ pab_dst->address_book->sort_rule = AB_SORT_RULE_NONE;
+
+ rc = adrbk_nlistadd(pab_dst->address_book,
+ (a_c_arg_t)new_entry_num, NULL, NULL,
+ abe->addr.list,
+ 0, 0, 0);
+
+ pab_dst->address_book->sort_rule = save_sort_rule;
+ }
+
+ if(rc != 0){
+ if(abe && abe->nickname)
+ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error saving %s: %s"), abe->nickname, error_description(errno));
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 5, _("Error saving entry: %s"), error_description(errno));
+ err++;
+ goto get_out;
+ }
+
+ copied_so_far++;
+ }
+
+ if(need_write){
+ int sort_happened = 0;
+
+ if(adrbk_write(pab_dst->address_book, 0, NULL, &sort_happened, 0, 1)){
+ err++;
+ goto get_out;
+ }
+
+ if(sort_happened)
+ ps_global->mangled_screen = 1;
+ }
+
+get_out:
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ restore_state(&state);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ ps_global->mangled_footer = 1;
+
+ if(err){
+ ret = 0;
+ if(need_write)
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Save only partially completed"));
+ else
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ }
+ else if (how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0) > 0){
+
+ ret = 1;
+ snprintf(tmp, sizeof(tmp), "Saved %d %s to %s",
+ how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0),
+ ((how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0)) > 1) ? "entries" : "entry",
+ pab_dst->abnick);
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 0, 4, tmp);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Warn should point to an array of size MAX_NICKNAME+1.
+ */
+void
+set_act_list_member(ACTION_LIST_S *al, a_c_arg_t numarg, PerAddrBook *pab_dst, PerAddrBook *pab, char *warn)
+{
+ AdrBk_Entry *abe1, *abe2;
+ adrbk_cntr_t num;
+
+ num = (adrbk_cntr_t)numarg;
+
+ al->pab = pab;
+ al->num = num;
+
+ /* skip if they're copying from and to same addrbook */
+ if(pab == pab_dst)
+ al->skip = 1;
+ else{
+ abe1 = adrbk_get_ae(pab->address_book, numarg);
+ if(abe1 && abe1->nickname && abe1->nickname[0]){
+ adrbk_cntr_t dst_enum;
+
+ abe2 = adrbk_lookup_by_nick(pab_dst->address_book,
+ abe1->nickname, &dst_enum);
+ /*
+ * This nickname already exists in the destn address book.
+ */
+ if(abe2){
+ /* If it isn't different, no problem. Check it out. */
+ if(abes_are_equal(abe1, abe2))
+ al->skip = 1;
+ else{
+ strncpy(warn, abe1->nickname, MAX_NICKNAME);
+ warn[MAX_NICKNAME] = '\0';
+ al->dup = 1;
+ al->num_in_dst = dst_enum;
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Print out the display list.
+ */
+int
+ab_print(int agg)
+{
+ int do_entry = 0, curopen;
+ char *prompt;
+
+ dprint((2, "- ab_print -\n"));
+
+ curopen = cur_is_open();
+ if(!agg && curopen){
+ static ESCKEY_S prt[] = {
+ {'a', 'a', "A", N_("AddressBook")},
+ {'e', 'e', "E", N_("Entry")},
+ {-1, 0, NULL, NULL}};
+
+ prompt = _("Print Address Book or just this Entry? ");
+ switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global), prt, 'a', 'x',
+ NO_HELP, RB_NORM)){
+ case 'x' :
+ q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
+ ps_global->mangled_footer = 1;
+ return 0;
+
+ case 'e':
+ do_entry = 1;
+ break;
+
+ default:
+ case 'a':
+ break;
+ }
+ }
+
+ /* TRANSLATORS: This is input for
+ Print something1 using something2. The thing we're
+ defining here is something1. */
+ if(agg)
+ prompt = _("selected entries");
+ else{
+ if(!curopen)
+ prompt = _("address book list");
+ else if(do_entry)
+ prompt = _("entry");
+ else
+ prompt = _("address book");
+ }
+
+ if(open_printer(prompt) == 0){
+ DL_CACHE_S dlc_buf, *match_dlc;
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ long save_line;
+ char *addr;
+ char spaces[100];
+ char more_spaces[100];
+ char b[500];
+ int abook_indent;
+
+ save_line = as.top_ent + as.cur_row;
+ match_dlc = get_dlc(save_line);
+ dlc_buf = *match_dlc;
+ match_dlc = &dlc_buf;
+
+ if(do_entry){ /* print an individual addrbook entry */
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ snprintf(spaces, sizeof(spaces), "%*.*s", abook_indent+2, abook_indent+2, "");
+ snprintf(more_spaces, sizeof(more_spaces), "%*.*s",
+ abook_indent+4, abook_indent+4, "");
+
+ dl = dlist(save_line);
+ abe = ae(save_line);
+
+ if(abe){
+ int are_some_unqualified = 0, expand_nicks = 0;
+ char *string, *tmp;
+ ADDRESS *adrlist = NULL;
+
+ /*
+ * Search through the addresses to see if there are any
+ * that are unqualified, and so would be different if
+ * expanded.
+ */
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+
+ if(are_some_unqualified){
+ switch(want_to("Expand nicknames", 'y', 'x', h_ab_forward,
+ WT_NORM)){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
+ ps_global->mangled_footer = 1;
+ return 0;
+
+ case 'y':
+ expand_nicks = 1;
+ break;
+
+ case 'n':
+ expand_nicks = 0;
+ break;
+ }
+ }
+
+ /* expand nicknames and fully-qualify unqualified names */
+ if(expand_nicks){
+ char *error = NULL;
+ BuildTo bldto;
+ char *init_addr = NULL;
+
+ if(abe->tag == Single)
+ init_addr = cpystr(abe->addr.addr);
+ else{
+ char **ll;
+ char *p;
+ long length;
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(ll = abe->addr.list; ll && *ll; ll++)
+ length += (strlen(*ll) + 2);
+
+ if(length)
+ length -= 2L;
+
+ init_addr = (char *)fs_get((size_t)(length+1L) *
+ sizeof(char));
+ p = init_addr;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ sstrncpy(&p, *ll, length-(p-init_addr));
+ if(*(ll+1))
+ sstrncpy(&p, ", ", length-(p-init_addr));
+ }
+
+ init_addr[length] = '\0';
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = init_addr;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(init_addr)
+ fs_give((void **)&init_addr);
+
+ if(error){
+ q_status_message1(SM_ORDER, 0, 4, "%s", error);
+ fs_give((void **)&error);
+
+ ps_global->mangled_footer = 1;
+ return 0;
+ }
+
+ if(addr){
+ rfc822_parse_adrlist(&adrlist, addr,
+ ps_global->maildomain);
+ fs_give((void **)&addr);
+ }
+
+ /* Will use adrlist to do the printing below */
+ }
+
+ tmp = abe->nickname ? abe->nickname : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->fullname ? abe->fullname : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->fcc ? abe->fcc : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->extra ? abe->extra : "";
+ {unsigned char *p, *bb = NULL;
+ size_t n, len;
+ if((n = 4*strlen(tmp)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = bb = (unsigned char *)fs_get(len * sizeof(char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *)tmp_20k_buf;
+ }
+
+ string = (char *)rfc1522_decode_to_utf8(p, len, tmp);
+ utf8_snprintf(b, len, "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ if(bb)
+ fs_give((void **)&bb);
+ }
+
+ /*
+ * Print addresses
+ */
+
+ if(expand_nicks){
+ ADDRESS *a;
+
+ for(a = adrlist; a; a = a->next){
+ char *bufp;
+ ADDRESS *next_addr;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ tmp = addr_string(a, bufp, len);
+ a->next = next_addr;
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf+10000,
+ SIZEOF_20KBUF-10000, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80,
+ (a == adrlist) ? b : spaces,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ fs_give((void **)&bufp);
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+ else{
+ utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
+ print_text(b);
+ }
+ }
+ else{ /* don't expand or qualify */
+ if(abe->tag == Single){
+ tmp = abe->addr.addr ? abe->addr.addr : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
+ SIZEOF_20KBUF-10000, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80, b,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ else{
+ char **ll;
+
+ if(!abe->addr.list || !abe->addr.list[0]){
+ utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
+ print_text(b);
+ }
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
+ SIZEOF_20KBUF-10000, *ll);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80,
+ (ll == abe->addr.list)
+ ? b : spaces,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+ }
+ }
+ }
+ else{
+ long lineno;
+ char lbuf[6*MAX_SCREEN_COLS + 1];
+ char *p;
+ int i, savecur, savezoomed;
+ OpenStatus savestatus;
+ PerAddrBook *pab;
+
+ if(agg){ /* print all selected entries */
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ savezoomed = as.zoomed;
+ /*
+ * Fool display code into thinking display is zoomed, so
+ * we'll skip the unselected entries. Since this feature
+ * causes all abooks to be displayed we don't have to
+ * step through each addrbook.
+ */
+ as.zoomed = 1;
+ warp_to_beginning();
+ lineno = 0L;
+ for(dl = dlist(lineno);
+ dl->type != End;
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ListEmpty:
+ case ClickHereCmb:
+ case Text:
+ case TitleCmb:
+ case Empty:
+ case ZoomEmpty:
+ case AskServer:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL,
+ NULL, lbuf, sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ as.zoomed = savezoomed;
+ }
+ else{ /* print all selected entries */
+ savecur = as.cur;
+ savezoomed = as.zoomed;
+ as.zoomed = 1;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(!(pab->address_book &&
+ any_selected(pab->address_book->selects)))
+ continue;
+
+ /*
+ * Print selected entries from addrbook i.
+ * We have to put addrbook i into Open state so
+ * that the display code will work right.
+ */
+ as.cur = i;
+ savestatus = pab->ostatus;
+ init_abook(pab, Open);
+ init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
+ (void)calculate_field_widths();
+ warp_to_beginning();
+ lineno = 0L;
+
+ for(dl = dlist(lineno);
+ dl->type != End;
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ClickHereCmb:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL,
+ NULL, lbuf,sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ init_abook(pab, savestatus);
+ }
+
+ as.cur = savecur;
+ as.zoomed = savezoomed;
+ /* restore the display for the current addrbook */
+ init_disp_form(&as.adrbks[as.cur],
+ ps_global->VAR_ABOOK_FORMATS, as.cur);
+ }
+ }
+ else{ /* print either the abook list or a single abook */
+ int anum;
+ DL_CACHE_S *dlc;
+
+ savezoomed = as.zoomed;
+ as.zoomed = 0;
+
+ if(curopen){ /* print a single address book */
+ anum = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+ warp_to_top_of_abook(anum);
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
+ lineno = 0L - XTRA_TITLE_LINES_IN_OLD_ABOOK_DISP;
+ else{
+ print_text(" ");
+ print_text(_("ADDRESS BOOK"));
+ print_text1(" %s\n\n", as.adrbks[as.cur].abnick);
+ lineno = 0L;
+ }
+ }
+ else{ /* print the list of address books */
+ warp_to_beginning();
+ lineno = 0L;
+ }
+
+ for(dl = dlist(lineno);
+ dl->type != End && (!curopen ||
+ (anum==adrbk_num_from_lineno(lineno) &&
+ (as.n_serv == 0 ||
+ ((dlc=get_dlc(lineno)) &&
+ dlc->type != DlcDirDelim1))));
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ClickHereCmb:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL, NULL,
+ lbuf, sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ as.zoomed = savezoomed;
+ }
+ }
+
+ close_printer();
+
+ /*
+ * jump cache back to where we started so that the next
+ * request won't cause us to page through the whole thing
+ */
+ if(!do_entry)
+ warp_to_dlc(match_dlc, save_line);
+
+ ps_global->mangled_screen = 1;
+ }
+
+ ps_global->mangled_footer = 1;
+ return 1;
+}
+
+
+/*
+ * Delete address book entries.
+ */
+int
+ab_agg_delete(struct pine *ps, int agg)
+{
+ int ret = 0, i, ch, rc = 0;
+ PerAddrBook *pab;
+ adrbk_cntr_t num, ab_count;
+ char prompt[80];
+
+ dprint((2, "- ab_agg_delete -\n"));
+
+ if(agg){
+ snprintf(prompt, sizeof(prompt), _("Really delete %d selected entries"), as.selections);
+ prompt[sizeof(prompt)-1] = '\0';
+ ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
+ if(ch == 'y'){
+ adrbk_cntr_t newelnum, flushelnum = NO_NEXT;
+ DL_CACHE_S dlc_save, dlc_restart, *dlc;
+ int we_cancel = 0;
+ int top_level_display;
+
+ /*
+ * We want to try to put the cursor in a reasonable position
+ * on the screen when we're done. If we are in the top-level
+ * display, then we can leave it the same. If we have an
+ * addrbook opened, then we want to see if we can get back to
+ * the same entry we are currently on.
+ */
+ if(!(top_level_display = !any_ab_open())){
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_save = *dlc;
+ newelnum = dlc_save.dlcelnum;
+ }
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ for(i = 0; i < as.n_addrbk && rc != -5; i++){
+ int orig_selected, selected;
+
+ pab = &as.adrbks[i];
+ if(!pab->address_book)
+ continue;
+
+ ab_count = adrbk_count(pab->address_book);
+ rc = 0;
+ selected = howmany_selected(pab->address_book->selects);
+ orig_selected = selected;
+ /*
+ * Because deleting an entry causes the addrbook to be
+ * immediately updated, we need to delete from higher entry
+ * numbers to lower numbers. That way, entry number n is still
+ * entry number n in the updated address book because we've
+ * only deleted entries higher than n.
+ */
+ for(num = ab_count-1; selected > 0 && rc == 0; num--){
+ if(entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)num)){
+ rc = adrbk_delete(pab->address_book, (a_c_arg_t)num,
+ 1, 0, 0, 0);
+
+ selected--;
+
+ /*
+ * This is just here to help us reposition the cursor.
+ */
+ if(!top_level_display && as.cur == i && rc == 0){
+ if(num >= newelnum)
+ flushelnum = num;
+ else
+ newelnum--;
+ }
+ }
+ }
+
+ if(rc == 0 && orig_selected > 0){
+ int sort_happened = 0;
+
+ rc = adrbk_write(pab->address_book, 0, NULL, &sort_happened, 1, 0);
+ if(sort_happened)
+ ps_global->mangled_screen = 1;
+ }
+
+ if(rc && rc != -5){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "Error updating %s: %s",
+ (as.n_addrbk > 1) ? pab->abnick
+ : "address book",
+ error_description(errno));
+ dprint((1, "Error updating %s: %s\n",
+ pab->filename ? pab->filename : "?",
+ error_description(errno)));
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(rc == 0){
+ q_status_message(SM_ORDER, 0, 2, _("Deletions completed"));
+ ret = 1;
+ erase_selections();
+ }
+
+ if(!top_level_display){
+ int lost = 0;
+ long new_ent;
+
+ if(flushelnum != dlc_save.dlcelnum){
+ /*
+ * We didn't delete current so restart there. The elnum
+ * may have changed if we deleted entries above it.
+ */
+ dlc_restart = dlc_save;
+ dlc_restart.dlcelnum = newelnum;
+ }
+ else{
+ /*
+ * Current was deleted.
+ */
+ dlc_restart.adrbk_num = as.cur;
+ pab = &as.adrbks[as.cur];
+ ab_count = adrbk_count(pab->address_book);
+ if(ab_count == 0)
+ dlc_restart.type = DlcEmpty;
+ else{
+ AdrBk_Entry *abe;
+
+ dlc_restart.dlcelnum = MIN(newelnum, ab_count-1);
+ abe = adrbk_get_ae(pab->address_book,
+ (a_c_arg_t) dlc_restart.dlcelnum);
+ if(abe && abe->tag == Single)
+ dlc_restart.type = DlcSimple;
+ else if(abe && abe->tag == List)
+ dlc_restart.type = DlcListHead;
+ else
+ lost++;
+ }
+ }
+
+ if(lost){
+ warp_to_top_of_abook(as.cur);
+ as.top_ent = 0L;
+ new_ent = first_selectable_line(0L);
+ if(new_ent == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = new_ent;
+
+ /* if it is off screen */
+ 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);
+ }
+ }
+ else if(dlc_restart.type != DlcEmpty &&
+ dlc_restart.dlcelnum == dlc_save.dlcelnum &&
+ (F_OFF(F_CMBND_ABOOK_DISP,ps_global) || as.cur == 0)){
+ /*
+ * Didn't delete any before this line.
+ * Leave screen about the same. (May have deleted current.)
+ */
+ warp_to_dlc(&dlc_restart, as.cur_row+as.top_ent);
+ }
+ else{
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+
+ ps->mangled_body = 1;
+ }
+ }
+ else
+ cmd_cancelled("Apply Delete command");
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Delete an entry from the address book
+ *
+ * Args: abook -- The addrbook handle into access library
+ * command_line -- The screen line on which to prompt
+ * cur_line -- The entry number in the display list
+ * warped -- We warped to a new part of the addrbook
+ *
+ * Result: returns 1 if an entry was deleted, 0 if not.
+ *
+ * The main routine above knows what to repaint because it's always the
+ * current entry that's deleted. Here confirmation is asked of the user
+ * and the appropriate adrbklib functions are called.
+ */
+int
+single_entry_delete(AdrBk *abook, long int cur_line, int *warped)
+{
+ char ch, *cmd, *dname;
+ char prompt[200];
+ int rc;
+ register AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ DL_CACHE_S *dlc_to_flush;
+
+ dprint((2, "- single_entry_delete -\n"));
+
+ if(warped)
+ *warped = 0;
+
+ dl = dlist(cur_line);
+ abe = adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+
+ switch(dl->type){
+ case Simple:
+ dname = (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : abe->nickname ? abe->nickname : "";
+ cmd = _("Really delete \"%s\"");
+ break;
+
+ case ListHead:
+ dname = (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : abe->nickname ? abe->nickname : "";
+ cmd = _("Really delete ENTIRE list \"%s\"");
+ break;
+
+ case ListEnt:
+ dname = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, listmem_from_dl(abook, dl));
+ cmd = _("Really delete \"%s\" from list");
+ break;
+
+ default:
+ break;
+ }
+
+ dname = dname ? dname : "";
+ cmd = cmd ? cmd : "";
+
+ snprintf(prompt, sizeof(prompt), cmd, dname);
+ prompt[sizeof(prompt)-1] = '\0';
+ ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
+ if(ch == 'y'){
+ dlc_to_flush = get_dlc(cur_line);
+ if(dl->type == Simple || dl->type == ListHead){
+ /*--- Kill a single entry or an entire list ---*/
+ rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 1, 1, 0, 1);
+ }
+ else if(listmem_count_from_abe(abe) > 2){
+ /*---- Kill an entry out of a list ----*/
+ rc = adrbk_listdel(abook, (a_c_arg_t)dl->elnum,
+ listmem_from_dl(abook, dl));
+ }
+ else{
+ char *nick, *full, *addr, *fcc, *comment;
+ adrbk_cntr_t new_entry_num = NO_NEXT;
+
+ /*---- Convert a List to a Single entry ----*/
+
+ /* Save old info to be transferred */
+ nick = cpystr(abe->nickname);
+ full = cpystr(abe->fullname);
+ fcc = cpystr(abe->fcc);
+ comment = cpystr(abe->extra);
+ if(listmem_count_from_abe(abe) == 2)
+ addr = cpystr(abe->addr.list[1 - dl->l_offset]);
+ else
+ addr = cpystr("");
+
+ rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 0, 1, 0, 0);
+ if(rc == 0)
+ adrbk_add(abook,
+ NO_NEXT,
+ nick,
+ full,
+ addr,
+ fcc,
+ comment,
+ Single,
+ &new_entry_num,
+ (int *)NULL,
+ 1,
+ 0,
+ 1);
+
+ fs_give((void **)&nick);
+ fs_give((void **)&full);
+ fs_give((void **)&fcc);
+ fs_give((void **)&comment);
+ fs_give((void **)&addr);
+
+ if(rc == 0){
+ DL_CACHE_S dlc_restart;
+
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.dlcelnum = new_entry_num;
+ dlc_restart.type = DlcSimple;
+ warp_to_dlc(&dlc_restart, 0L);
+ *warped = 1;
+ return 1;
+ }
+ }
+
+ if(rc == 0){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Entry deleted, address book updated"));
+ dprint((5, "abook: Entry %s\n",
+ (dl->type == Simple || dl->type == ListHead) ? "deleted"
+ : "modified"));
+ /*
+ * Remove deleted line and everything after it from
+ * the dlc cache. Next time we try to access those lines they
+ * will get filled in with the right info.
+ */
+ flush_dlc_from_cache(dlc_to_flush);
+ return 1;
+ }
+ else{
+ PerAddrBook *pab;
+
+ if(rc != -5)
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error updating address book: %s"),
+ error_description(errno));
+ pab = &as.adrbks[as.cur];
+ dprint((1, "Error deleting entry from %s (%s): %s\n",
+ pab->abnick ? pab->abnick : "?",
+ pab->filename ? pab->filename : "?",
+ error_description(errno)));
+ }
+
+ return 0;
+ }
+ else{
+ q_status_message(SM_INFO, 0, 2, _("Entry not deleted"));
+ return 0;
+ }
+}
+
+
+void
+free_headents(struct headerentry **head)
+{
+ struct headerentry *he;
+ PrivateTop *pt;
+
+ if(head && *head){
+ for(he = *head; he->name; he++)
+ if(he->bldr_private){
+ pt = (PrivateTop *)he->bldr_private;
+ free_privatetop(&pt);
+ }
+
+ fs_give((void **)head);
+ }
+}
+
+
+#ifdef ENABLE_LDAP
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_query[]={
+ {"Normal Search : ", "NormalSearch", h_composer_qserv_qq, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Name : ", "Name", h_composer_qserv_cn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Surname : ", "SurName", h_composer_qserv_sn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Given Name : ", "GivenName", h_composer_qserv_gn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Email Address : ", "EmailAddress", h_composer_qserv_mail, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Organization : ", "Organization", h_composer_qserv_org, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Org Unit : ", "OrganizationalUnit", h_composer_qserv_unit, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Country : ", "Country", h_composer_qserv_country, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"State : ", "State", h_composer_qserv_state, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Locality : ", "Locality", h_composer_qserv_locality, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {" ", "BlankLine", NO_HELP, 1, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, KS_NONE},
+ {"Custom Filter : ", "CustomFilter", h_composer_qserv_custom, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define QQ_QQ 0
+#define QQ_CN 1
+#define QQ_SN 2
+#define QQ_GN 3
+#define QQ_MAIL 4
+#define QQ_ORG 5
+#define QQ_UNIT 6
+#define QQ_COUNTRY 7
+#define QQ_STATE 8
+#define QQ_LOCALITY 9
+#define QQ_BLANK 10
+#define QQ_CUSTOM 11
+#define QQ_END 12
+
+static SAVED_QUERY_S *saved_params;
+
+
+/*
+ * Formulate a query for LDAP servers and send it and view it.
+ * This is called from the Address Book screen, either from ^T from the
+ * composer (selecting) or just when browsing in the address book screen
+ * (selecting == 0).
+ *
+ * Args ps -- Pine struct
+ * selecting -- This is set if we're just selecting an address, as opposed
+ * to browsing on an LDAP server
+ * who -- Tells us which server to query
+ * error -- An error message allocated here and freed by caller.
+ *
+ * Returns -- Null if not selecting, possibly an address if selecting.
+ * The address is 1522 decoded and should be freed by the caller.
+ */
+char *
+query_server(struct pine *ps, int selecting, int *exit, int who, char **error)
+{
+ struct headerentry *he = NULL;
+ PICO pbf;
+ STORE_S *msgso = NULL;
+ int i, lret, editor_result;
+ int r = 4, flags;
+ HelpType help = NO_HELP;
+#define FILTSIZE 1000
+ char fbuf[FILTSIZE+1];
+ char *ret = NULL;
+ LDAP_CHOOSE_S *winning_e = NULL;
+ LDAP_SERV_RES_S *free_when_done = NULL;
+ SAVED_QUERY_S *sq = NULL;
+ static ESCKEY_S ekey[] = {
+ /* TRANSLATORS: go to more complex search screen */
+ {ctrl('T'), 10, "^T", N_("To complex search")},
+ {-1, 0, NULL, NULL}
+ };
+
+ dprint((2, "- query_server(%s) -\n", selecting?"Selecting":""));
+
+ if(!(ps->VAR_LDAP_SERVERS && ps->VAR_LDAP_SERVERS[0] &&
+ ps->VAR_LDAP_SERVERS[0][0])){
+ if(error)
+ *error = cpystr(_("No LDAP server available for lookup"));
+
+ return(ret);
+ }
+
+ fbuf[0] = '\0';
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+ while(r == 4 || r == 3){
+ /* TRANSLATORS: we're asking for a character string to search for */
+ r = optionally_enter(fbuf, -FOOTER_ROWS(ps), 0, sizeof(fbuf),
+ _("String to search for : "),
+ ekey, help, &flags);
+ if(r == 3)
+ help = help == NO_HELP ? h_dir_comp_search : NO_HELP;
+ }
+
+ /* strip quotes that user typed by mistake */
+ (void)removing_double_quotes(fbuf);
+
+ if(r == 1 || (r != 10 && fbuf[0] == '\0')){
+ ps->mangled_footer = 1;
+ if(error)
+ *error = cpystr(_("Cancelled"));
+
+ return(ret);
+ }
+
+ editor_result = COMP_EXIT; /* just to get right logic below */
+
+ memset((void *)&pbf, 0, sizeof(pbf));
+
+ if(r == 10){
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_simpleexit;
+ pbf.exit_label = _("Search");
+ pbf.canceltest = pico_simplecancel;
+ if(saved_params){
+ pbf.expander = restore_query_parameters;
+ pbf.ctrlr_label = _("Restore");
+ }
+
+ pbf.pine_anchor = set_titlebar(_("SEARCH DIRECTORY SERVER"),
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,
+ ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them. So just make each line a whole sentence
+ * that doesn't need the others below it to make sense.
+ */
+ so_puts(msgso,
+_("\n Fill in some of the fields above to create a query."));
+ so_puts(msgso,
+_("\n The match will be for the exact string unless you include wildcards (*)."));
+ so_puts(msgso,
+_("\n All filled-in fields must match in order to be counted as a match."));
+ so_puts(msgso,
+_("\n Press \"^R\" to restore previous query values (if you've queried previously)."));
+ so_puts(msgso,
+_("\n \"^G\" for help specific to each item. \"^X\" to make the query, or \"^C\" to cancel."));
+ }
+
+ he = (struct headerentry *)fs_get((QQ_END+1) *
+ sizeof(struct headerentry));
+ memset((void *)he, 0, (QQ_END+1) * sizeof(struct headerentry));
+ for(i = QQ_QQ; i <= QQ_END; i++)
+ he[i] = headents_for_query[i];
+
+ pbf.headents = he;
+
+ sq = copy_query_parameters(NULL);
+ he[QQ_QQ].realaddr = &sq->qq;
+ he[QQ_CN].realaddr = &sq->cn;
+ he[QQ_SN].realaddr = &sq->sn;
+ he[QQ_GN].realaddr = &sq->gn;
+ he[QQ_MAIL].realaddr = &sq->mail;
+ he[QQ_ORG].realaddr = &sq->org;
+ he[QQ_UNIT].realaddr = &sq->unit;
+ he[QQ_COUNTRY].realaddr = &sq->country;
+ he[QQ_STATE].realaddr = &sq->state;
+ he[QQ_LOCALITY].realaddr = &sq->locality;
+ he[QQ_CUSTOM].realaddr = &sq->custom;
+
+ /* pass to pico and let user set them */
+ editor_result = pico(&pbf);
+ ps->mangled_screen = 1;
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP)
+ hup_signal();
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+ }
+
+ if(editor_result & COMP_EXIT &&
+ ((r == 0 && *fbuf) ||
+ (r == 10 && sq &&
+ (*sq->qq || *sq->cn || *sq->sn || *sq->gn || *sq->mail ||
+ *sq->org || *sq->unit || *sq->country || *sq->state ||
+ *sq->locality || *sq->custom)))){
+ LDAPLookupStyle style;
+ WP_ERR_S wp_err;
+ int need_and, mangled;
+ char *string;
+ CUSTOM_FILT_S *filter;
+ SAVED_QUERY_S *s;
+
+ s = copy_query_parameters(sq);
+ save_query_parameters(s);
+
+ if(r == 0){
+ string = fbuf;
+ filter = NULL;
+ }
+ else{
+ int categories = 0;
+
+ categories = ((*sq->cn != '\0') ? 1 : 0) +
+ ((*sq->sn != '\0') ? 1 : 0) +
+ ((*sq->gn != '\0') ? 1 : 0) +
+ ((*sq->mail != '\0') ? 1 : 0) +
+ ((*sq->org != '\0') ? 1 : 0) +
+ ((*sq->unit != '\0') ? 1 : 0) +
+ ((*sq->country != '\0') ? 1 : 0) +
+ ((*sq->state != '\0') ? 1 : 0) +
+ ((*sq->locality != '\0') ? 1 : 0);
+ need_and = (categories > 1);
+
+ if((sq->cn ? strlen(sq->cn) : 0 +
+ sq->sn ? strlen(sq->sn) : 0 +
+ sq->gn ? strlen(sq->gn) : 0 +
+ sq->mail ? strlen(sq->mail) : 0 +
+ sq->org ? strlen(sq->org) : 0 +
+ sq->unit ? strlen(sq->unit) : 0 +
+ sq->country ? strlen(sq->country) : 0 +
+ sq->state ? strlen(sq->state) : 0 +
+ sq->locality ? strlen(sq->locality) : 0) > FILTSIZE - 100){
+ if(error)
+ *error = cpystr(_("Search strings too long"));
+
+ goto all_done;
+ }
+
+ if(categories > 0){
+
+ snprintf(fbuf, sizeof(fbuf),
+ "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ need_and ? "(&" : "",
+ *sq->cn ? "(cn=" : "",
+ *sq->cn ? sq->cn : "",
+ *sq->cn ? ")" : "",
+ *sq->sn ? "(sn=" : "",
+ *sq->sn ? sq->sn : "",
+ *sq->sn ? ")" : "",
+ *sq->gn ? "(givenname=" : "",
+ *sq->gn ? sq->gn : "",
+ *sq->gn ? ")" : "",
+ *sq->mail ? "(mail=" : "",
+ *sq->mail ? sq->mail : "",
+ *sq->mail ? ")" : "",
+ *sq->org ? "(o=" : "",
+ *sq->org ? sq->org : "",
+ *sq->org ? ")" : "",
+ *sq->unit ? "(ou=" : "",
+ *sq->unit ? sq->unit : "",
+ *sq->unit ? ")" : "",
+ *sq->country ? "(c=" : "",
+ *sq->country ? sq->country : "",
+ *sq->country ? ")" : "",
+ *sq->state ? "(st=" : "",
+ *sq->state ? sq->state : "",
+ *sq->state ? ")" : "",
+ *sq->locality ? "(l=" : "",
+ *sq->locality ? sq->locality : "",
+ *sq->locality ? ")" : "",
+ need_and ? ")" : "");
+ fbuf[sizeof(fbuf)-1] = '\0';
+ }
+
+ if(categories > 0 || *sq->custom)
+ filter = (CUSTOM_FILT_S *)fs_get(sizeof(CUSTOM_FILT_S));
+
+ /* combine the configed filters with this filter */
+ if(*sq->custom){
+ string = "";
+ filter->filt = sq->custom;
+ filter->combine = 0;
+ }
+ else if(*sq->qq && categories > 0){
+ string = sq->qq;
+ filter->filt = fbuf;
+ filter->combine = 1;
+ }
+ else if(categories > 0){
+ string = "";
+ filter->filt = fbuf;
+ filter->combine = 0;
+ }
+ else{
+ string = sq->qq;
+ filter = NULL;
+ }
+ }
+
+ mangled = 0;
+ memset(&wp_err, 0, sizeof(wp_err));
+ wp_err.mangled = &mangled;
+ style = selecting ? AlwaysDisplayAndMailRequired : AlwaysDisplay;
+
+ /* maybe coming from composer */
+ fix_windsize(ps_global);
+ init_sigwinch();
+ clear_cursor_pos();
+
+ lret = ldap_lookup_all(string, who, 0, style, filter, &winning_e,
+ &wp_err, &free_when_done);
+
+ if(filter)
+ fs_give((void **)&filter);
+
+ if(wp_err.mangled)
+ ps->mangled_screen = 1;
+
+ if(wp_err.error){
+ if(status_message_remaining() && error)
+ *error = wp_err.error;
+ else
+ fs_give((void **)&wp_err.error);
+ }
+
+ if(lret == 0 && winning_e && selecting){
+ ADDRESS *addr;
+
+ addr = address_from_ldap(winning_e);
+ if(addr){
+ if(!addr->host){
+ addr->host = cpystr("missing-hostname");
+ if(error){
+ if(*error)
+ fs_give((void **)error);
+
+ *error = cpystr(_("Missing hostname in LDAP address"));
+ }
+ }
+
+ ret = addr_list_string(addr, NULL, 1);
+ if(!ret || !ret[0]){
+ if(ret)
+ fs_give((void **)&ret);
+
+ if(exit)
+ *exit = 1;
+
+ if(error && !*error){
+ char buf[200];
+
+ snprintf(buf, sizeof(buf), _("No email address available for \"%s\""),
+ (addr->personal && *addr->personal)
+ ? addr->personal
+ : "selected entry");
+ buf[sizeof(buf)-1] = '\0';
+ *error = cpystr(buf);
+ }
+ }
+
+ mail_free_address(&addr);
+ }
+ }
+ else if(lret == -1 && exit)
+ *exit = 1;
+ }
+
+all_done:
+ if(he)
+ free_headents(&he);
+
+ if(free_when_done)
+ free_ldap_result_list(&free_when_done);
+
+ if(winning_e)
+ fs_give((void **)&winning_e);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(sq)
+ free_query_parameters(&sq);
+
+ return(ret);
+}
+
+
+/*
+ * View all fields of an LDAP entry while browsing.
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+view_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
+{
+ STORE_S *srcstore = NULL;
+ SourceType srctype = CharStar;
+ SCROLL_S sargs;
+ HANDLE_S *handles = NULL;
+
+ dprint((9, "- view_ldap_entry -\n"));
+
+ if((srcstore=prep_ldap_for_viewing(ps,winning_e,srctype,&handles)) != NULL){
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(srcstore);
+ sargs.text.src = srctype;
+ sargs.text.desc = _("expanded entry");
+ sargs.text.handles= handles;
+ sargs.bar.title = _("DIRECTORY ENTRY");
+ sargs.proc.tool = process_ldap_cmd;
+ sargs.proc.data.p = (void *) winning_e;
+ sargs.help.text = h_ldap_view;
+ sargs.help.title = _("HELP FOR DIRECTORY VIEW");
+ sargs.keys.menu = &ldap_view_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ if(handles)
+ sargs.keys.menu->how_many = 2;
+ else{
+ sargs.keys.menu->how_many = 1;
+ clrbitn(OTHER_KEY, sargs.keys.bitmap);
+ }
+
+ scrolltool(&sargs);
+
+ ps->mangled_screen = 1;
+ so_give(&srcstore);
+ free_handles(&handles);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+}
+
+
+/*
+ * Compose a message to the email addresses contained in an LDAP entry.
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+compose_to_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *e, int allow_role)
+{
+ char **elecmail = NULL,
+ **mail = NULL,
+ **cn = NULL,
+ **sn = NULL,
+ **givenname = NULL,
+ **ll;
+ size_t len = 0;
+
+ dprint((9, "- compose_to_ldap_entry -\n"));
+
+ if(e){
+ char *a;
+ BerElement *ber;
+
+ for(a = ldap_first_attribute(e->ld, e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(e->ld, e->selected_entry, ber)){
+
+ if(strcmp(a, e->info_used->mailattr) == 0){
+ if(!mail)
+ mail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "electronicmail") == 0){
+ if(!elecmail)
+ elecmail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->cnattr) == 0){
+ if(!cn)
+ cn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->gnattr) == 0){
+ if(!givenname)
+ givenname = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->snattr) == 0){
+ if(!sn)
+ sn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+
+ our_ldap_memfree(a);
+ }
+ }
+
+ if(elecmail){
+ if(elecmail[0] && elecmail[0][0] && !mail)
+ mail = elecmail;
+ else
+ ldap_value_free(elecmail);
+
+ elecmail = NULL;
+ }
+
+ for(ll = mail; ll && *ll; ll++)
+ len += strlen(*ll) + 1;
+
+ if(len){
+ char *p, *address, *fn = NULL;
+ BuildTo bldto;
+ SAVE_STATE_S state;
+
+ address = (char *)fs_get(len * sizeof(char));
+ p = address;
+ ll = mail;
+ while(*ll){
+ sstrncpy(&p, *ll, len-(p-address));
+ ll++;
+ if(*ll)
+ sstrncpy(&p, ",", len-(p-address));
+ }
+
+ address[len-1] = '\0';
+
+ /*
+ * If we have a fullname and there is only a single address and
+ * the address doesn't seem to have a fullname with it, add it.
+ */
+ if(mail && mail[0] && mail[0][0] && !mail[1]){
+ if(cn && cn[0] && cn[0][0])
+ fn = cpystr(cn[0]);
+ else if(sn && sn[0] && sn[0][0] &&
+ givenname && givenname[0] && givenname[0][0]){
+ size_t l;
+
+ l = strlen(givenname[0]) + strlen(sn[0]) + 1;
+ fn = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(fn, l+1, "%s %s", givenname[0], sn[0]);
+ fn[l] = '\0';
+ }
+ }
+
+ if(mail && mail[0] && mail[0][0] && !mail[1] && fn){
+ ADDRESS *adrlist = NULL;
+ char *tmp_a_string;
+
+ tmp_a_string = cpystr(address);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(adrlist && !adrlist->next && !adrlist->personal){
+ char *new_address;
+ size_t len;
+ RFC822BUFFER rbuf;
+
+ adrlist->personal = cpystr(fn);
+ len = est_size(adrlist);
+ new_address = (char *) fs_get(len * sizeof(char));
+ new_address[0] ='\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = new_address;
+ rbuf.cur = new_address;
+ rbuf.end = new_address+len-1;
+ /* this will quote it if it needs quoting */
+ rfc822_output_address_list(&rbuf, adrlist, 0L, NULL);
+ *rbuf.cur = '\0';
+ fs_give((void **)&address);
+ address = new_address;
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = address;
+
+ save_state(&state);
+ ab_compose_internal(bldto, allow_role);
+ restore_state(&state);
+ fs_give((void **)&address);
+ if(fn)
+ fs_give((void **)&fn);
+
+ /*
+ * Window size may have changed in composer.
+ * Pine_send will have reset the window size correctly,
+ * but we still have to reset our address book data structures.
+ */
+ if(as.initialized)
+ ab_resize();
+
+ ps_global->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, _("No address to compose to"));
+
+ if(mail)
+ ldap_value_free(mail);
+ if(cn)
+ ldap_value_free(cn);
+ if(sn)
+ ldap_value_free(sn);
+ if(givenname)
+ ldap_value_free(givenname);
+}
+
+
+/*
+ * Forward the text of an LDAP entry via email (not a vcard attachment)
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+forward_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
+{
+ STORE_S *srcstore = NULL;
+ SourceType srctype = CharStar;
+
+ dprint((9, "- forward_ldap_entry -\n"));
+
+ if((srcstore = prep_ldap_for_viewing(ps,winning_e,srctype,NULL)) != NULL){
+ forward_text(ps, so_text(srcstore), srctype);
+ ps->mangled_screen = 1;
+ so_give(&srcstore);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+}
+
+
+STORE_S *
+prep_ldap_for_viewing(struct pine *ps, LDAP_CHOOSE_S *winning_e, SourceType srctype, HANDLE_S **handlesp)
+{
+ STORE_S *store = NULL;
+ char *a, *tmp;
+ BerElement *ber;
+ int i, width;
+#define W (1000)
+#define INDENTHERE (22)
+ char obuf[W+10];
+ char hdr[6*INDENTHERE+1], hdr2[6*INDENTHERE+1];
+ char **cn = NULL;
+ int indent = INDENTHERE;
+
+ if(!(store = so_get(srctype, NULL, EDIT_ACCESS)))
+ return(store);
+
+ /* for mailto handles so user can select individual email addrs */
+ if(handlesp)
+ init_handles(handlesp);
+
+ width = MAX(ps->ttyo->screen_cols, 25);
+
+ snprintf(hdr2, sizeof(hdr2), "%-*.*s: ", indent-2,indent-2, "");
+ hdr2[sizeof(hdr2)-1] = '\0';
+
+ if(sizeof(obuf) > ps->ttyo->screen_cols+1){
+ memset((void *)obuf, '-', ps->ttyo->screen_cols * sizeof(char));
+ obuf[ps->ttyo->screen_cols] = '\n';
+ obuf[ps->ttyo->screen_cols+1] = '\0';
+ }
+
+ a = ldap_get_dn(winning_e->ld, winning_e->selected_entry);
+
+ so_puts(store, obuf);
+ if((tmp = fold(a, width, width, "", " ", FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+
+ so_puts(store, obuf);
+ so_puts(store, "\n");
+
+ our_ldap_dn_memfree(a);
+
+ for(a = ldap_first_attribute(winning_e->ld, winning_e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(winning_e->ld, winning_e->selected_entry, ber)){
+
+ if(a && *a){
+ char **vals;
+ char *fn = NULL;
+
+ vals = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+
+ /* save this for mailto */
+ if(handlesp && !cn && !strcmp(a, winning_e->info_used->cnattr))
+ cn = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+
+ if(vals){
+ int do_mailto;
+
+ do_mailto = (handlesp &&
+ !strcmp(a, winning_e->info_used->mailattr));
+
+ utf8_snprintf(hdr, sizeof(hdr), "%-*.*w: ", indent-2,indent-2,
+ ldap_translate(a, winning_e->info_used));
+ hdr[sizeof(hdr)-1] = '\0';
+ for(i = 0; vals[i] != NULL; i++){
+ if(do_mailto){
+ ADDRESS *ad = NULL;
+ HANDLE_S *h;
+ char buf[20];
+ char *tmp_a_string;
+ char *addr, *new_addr, *enc_addr;
+ char *path = NULL;
+
+ addr = cpystr(vals[i]);
+ if(cn && cn[0] && cn[0][0])
+ fn = cpystr(cn[0]);
+
+ if(fn){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&ad, tmp_a_string, "@");
+ fs_give((void **)&tmp_a_string);
+ if(ad && !ad->next && !ad->personal){
+ RFC822BUFFER rbuf;
+ size_t len;
+
+ ad->personal = cpystr(fn);
+ len = est_size(ad);
+ new_addr = (char *) fs_get(len * sizeof(char));
+ new_addr[0] = '\0';
+ /* this will quote it if it needs quoting */
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = new_addr;
+ rbuf.cur = new_addr;
+ rbuf.end = new_addr+len-1;
+ rfc822_output_address_list(&rbuf, ad, 0L, NULL);
+ *rbuf.cur = '\0';
+ fs_give((void **) &addr);
+ addr = new_addr;
+ }
+
+ if(ad)
+ mail_free_address(&ad);
+
+ fs_give((void **)&fn);
+ }
+
+ if((enc_addr = rfc1738_encode_mailto(addr)) != NULL){
+ size_t l;
+
+ l = strlen(enc_addr) + 7;
+ path = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(path, l+1, "mailto:%s", enc_addr);
+ path[l] = '\0';
+ fs_give((void **)&enc_addr);
+ }
+
+ fs_give((void **)&addr);
+
+ if(path){
+ h = new_handle(handlesp);
+ h->type = URL;
+ h->h.url.path = path;
+ snprintf(buf, sizeof(buf), "%d", h->key);
+ buf[sizeof(buf)-1] = '\0';
+
+ /*
+ * Don't try to fold this address. Just put it on
+ * one line and let scrolltool worry about
+ * cutting it off before it goes past the
+ * right hand edge. Otherwise, we have to figure
+ * out how to handle the wrapped handle.
+ */
+ snprintf(obuf, sizeof(obuf), "%c%c%c%s%s%c%c%c%c",
+ TAG_EMBED, TAG_HANDLE,
+ strlen(buf), buf, vals[i],
+ TAG_EMBED, TAG_BOLDOFF,
+ TAG_EMBED, TAG_INVOFF);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ so_puts(store, (i==0) ? hdr : hdr2);
+ so_puts(store, obuf);
+ so_puts(store, "\n");
+ }
+ else{
+ snprintf(obuf, sizeof(obuf), "%s", vals[i]);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ if((tmp = fold(obuf, width, width,
+ (i==0) ? hdr : hdr2,
+ repeat_char(indent+2, SPACE),
+ FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+ else{
+ snprintf(obuf, sizeof(obuf), "%s", vals[i]);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ if((tmp = fold(obuf, width, width,
+ (i==0) ? hdr : hdr2,
+ repeat_char(indent+2, SPACE),
+ FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ ldap_value_free(vals);
+ }
+ else{
+ utf8_snprintf(obuf, sizeof(obuf), "%-*.*w\n", indent-1,indent-1,
+ ldap_translate(a, winning_e->info_used));
+ obuf[sizeof(obuf)-1] = '\0';
+ so_puts(store, obuf);
+ }
+ }
+
+ our_ldap_memfree(a);
+ }
+
+ if(cn)
+ ldap_value_free(cn);
+
+ return(store);
+}
+
+
+int
+process_ldap_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_SAVE :
+ save_ldap_entry(ps_global, sparms->proc.data.p, 0);
+ rv = 0;
+ break;
+
+ case MC_COMPOSE :
+ compose_to_ldap_entry(ps_global, sparms->proc.data.p, 0);
+ rv = 0;
+ break;
+
+ case MC_ROLE :
+ compose_to_ldap_entry(ps_global, sparms->proc.data.p, 1);
+ rv = 0;
+ break;
+
+ default:
+ panic("Unexpected command in process_ldap_cmd");
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+pico_simpleexit(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
+ char **result)
+{
+ if(result)
+ *result = NULL;
+
+ return(0);
+}
+
+char *
+pico_simplecancel(void (*redraw_pico)(void))
+{
+ return("Cancelled");
+}
+
+
+/*
+ * Store query parameters so that they can be recalled by user later with ^R.
+ */
+void
+save_query_parameters(SAVED_QUERY_S *params)
+{
+ free_saved_query_parameters();
+ saved_params = params;
+}
+
+
+SAVED_QUERY_S *
+copy_query_parameters(SAVED_QUERY_S *params)
+{
+ SAVED_QUERY_S *sq;
+
+ sq = (SAVED_QUERY_S *)fs_get(sizeof(SAVED_QUERY_S));
+ memset((void *)sq, 0, sizeof(SAVED_QUERY_S));
+
+ if(params && params->qq)
+ sq->qq = cpystr(params->qq);
+ else
+ sq->qq = cpystr("");
+
+ if(params && params->cn)
+ sq->cn = cpystr(params->cn);
+ else
+ sq->cn = cpystr("");
+
+ if(params && params->sn)
+ sq->sn = cpystr(params->sn);
+ else
+ sq->sn = cpystr("");
+
+ if(params && params->gn)
+ sq->gn = cpystr(params->gn);
+ else
+ sq->gn = cpystr("");
+
+ if(params && params->mail)
+ sq->mail = cpystr(params->mail);
+ else
+ sq->mail = cpystr("");
+
+ if(params && params->org)
+ sq->org = cpystr(params->org);
+ else
+ sq->org = cpystr("");
+
+ if(params && params->unit)
+ sq->unit = cpystr(params->unit);
+ else
+ sq->unit = cpystr("");
+
+ if(params && params->country)
+ sq->country = cpystr(params->country);
+ else
+ sq->country = cpystr("");
+
+ if(params && params->state)
+ sq->state = cpystr(params->state);
+ else
+ sq->state = cpystr("");
+
+ if(params && params->locality)
+ sq->locality = cpystr(params->locality);
+ else
+ sq->locality = cpystr("");
+
+ if(params && params->custom)
+ sq->custom = cpystr(params->custom);
+ else
+ sq->custom = cpystr("");
+
+ return(sq);
+}
+
+
+void
+free_saved_query_parameters(void)
+{
+ if(saved_params)
+ free_query_parameters(&saved_params);
+}
+
+
+void
+free_query_parameters(SAVED_QUERY_S **parm)
+{
+ if(parm){
+ if(*parm){
+ if((*parm)->qq)
+ fs_give((void **)&(*parm)->qq);
+ if((*parm)->cn)
+ fs_give((void **)&(*parm)->cn);
+ if((*parm)->sn)
+ fs_give((void **)&(*parm)->sn);
+ if((*parm)->gn)
+ fs_give((void **)&(*parm)->gn);
+ if((*parm)->mail)
+ fs_give((void **)&(*parm)->mail);
+ if((*parm)->org)
+ fs_give((void **)&(*parm)->org);
+ if((*parm)->unit)
+ fs_give((void **)&(*parm)->unit);
+ if((*parm)->country)
+ fs_give((void **)&(*parm)->country);
+ if((*parm)->state)
+ fs_give((void **)&(*parm)->state);
+ if((*parm)->locality)
+ fs_give((void **)&(*parm)->locality);
+ if((*parm)->custom)
+ fs_give((void **)&(*parm)->custom);
+
+ fs_give((void **)parm);
+ }
+ }
+}
+
+
+/*
+ * A callback from pico to restore the saved query parameters.
+ *
+ * Args he -- Unused.
+ * s -- The place to return the allocated array of values.
+ *
+ * Returns -- 1 if there are parameters to return, 0 otherwise.
+ */
+int
+restore_query_parameters(struct headerentry *he, char ***s)
+{
+ int retval = 0, i = 0;
+
+ if(s)
+ *s = NULL;
+
+ if(saved_params && s){
+ *s = (char **)fs_get((QQ_END + 1) * sizeof(char *));
+ (*s)[i++] = cpystr(saved_params->qq ? saved_params->qq : "");
+ (*s)[i++] = cpystr(saved_params->cn ? saved_params->cn : "");
+ (*s)[i++] = cpystr(saved_params->sn ? saved_params->sn : "");
+ (*s)[i++] = cpystr(saved_params->gn ? saved_params->gn : "");
+ (*s)[i++] = cpystr(saved_params->mail ? saved_params->mail : "");
+ (*s)[i++] = cpystr(saved_params->org ? saved_params->org : "");
+ (*s)[i++] = cpystr(saved_params->unit ? saved_params->unit : "");
+ (*s)[i++] = cpystr(saved_params->country ? saved_params->country : "");
+ (*s)[i++] = cpystr(saved_params->state ? saved_params->state : "");
+ (*s)[i++] = cpystr(saved_params->locality ? saved_params->locality :"");
+ (*s)[i++] = cpystr(saved_params->custom ? saved_params->custom :"");
+ (*s)[i] = 0;
+ retval = 1;
+ }
+
+ return(retval);
+}
+
+
+/*
+ * Internal handler for viewing an LDAP url.
+ */
+int
+url_local_ldap(char *url)
+{
+ LDAP *ld;
+ struct timeval t;
+ int ld_err, mangled = 0, we_cancel, retval = 0, proto = 3;
+ int we_turned_on = 0;
+ char ebuf[300];
+ LDAPMessage *result;
+ LDAP_SERV_S *info;
+ LDAP_SERV_RES_S *serv_res = NULL;
+ LDAPURLDesc *ldapurl = NULL;
+ WP_ERR_S wp_err;
+
+ dprint((2, "url_local_ldap(%s)\n", url ? url : "?"));
+
+ ld_err = ldap_url_parse(url, &ldapurl);
+ if(ld_err || !ldapurl){
+ snprintf(ebuf, sizeof(ebuf), "URL parse failed for %s", url);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ return(retval);
+ }
+
+ if(!ldapurl->lud_host){
+ /* TRNASLATORS: No host in <url> */
+ snprintf(ebuf, sizeof(ebuf), _("No host in %s"), url);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ ldap_free_urldesc(ldapurl);
+ return(retval);
+ }
+
+ we_turned_on = intr_handling_on();
+ we_cancel = busy_cue(_("Searching for LDAP url"), NULL, 0);
+ ps_global->mangled_footer = 1;
+
+#if (LDAPAPI >= 11)
+ if((ld = ldap_init(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
+#else
+ if((ld = ldap_open(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
+#endif
+ {
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ q_status_message(SM_ORDER,3,5, _("LDAP search failed: can't initialize"));
+ }
+ else if(!ps_global->intr_pending){
+ if(ldap_v3_is_supported(ld) &&
+ our_ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) == 0){
+ dprint((5, "ldap: using version 3 protocol\n"));
+ }
+
+ /*
+ * If we don't set RESTART then the select() waiting for the answer
+ * in libldap will be interrupted and stopped by our busy_cue.
+ */
+ our_ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
+
+ t.tv_sec = 30; t.tv_usec = 0;
+ ld_err = ldap_search_st(ld, ldapurl->lud_dn, ldapurl->lud_scope,
+ ldapurl->lud_filter, ldapurl->lud_attrs,
+ 0, &t, &result);
+ if(ld_err != LDAP_SUCCESS){
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s"), ldap_err2string(ld_err));
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ ldap_unbind(ld);
+ }
+ else if(!ps_global->intr_pending){
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ if(we_turned_on){
+ intr_handling_off();
+ we_turned_on = 0;
+ }
+
+ if(ldap_count_entries(ld, result) == 0){
+ q_status_message(SM_ORDER, 3, 5, _("No matches found for url"));
+ ldap_unbind(ld);
+ if(result)
+ ldap_msgfree(result);
+ }
+ else{
+ serv_res = (LDAP_SERV_RES_S *)fs_get(sizeof(LDAP_SERV_RES_S));
+ memset((void *)serv_res, 0, sizeof(*serv_res));
+ serv_res->ld = ld;
+ serv_res->res = result;
+ info = (LDAP_SERV_S *)fs_get(sizeof(LDAP_SERV_S));
+ memset((void *)info, 0, sizeof(*info));
+ info->mailattr = cpystr(DEF_LDAP_MAILATTR);
+ info->snattr = cpystr(DEF_LDAP_SNATTR);
+ info->gnattr = cpystr(DEF_LDAP_GNATTR);
+ info->cnattr = cpystr(DEF_LDAP_CNATTR);
+ serv_res->info_used = info;
+ memset(&wp_err, 0, sizeof(wp_err));
+ wp_err.mangled = &mangled;
+
+ ask_user_which_entry(serv_res, NULL, NULL, &wp_err, DisplayForURL);
+ if(wp_err.error){
+ q_status_message(SM_ORDER, 3, 5, wp_err.error);
+ fs_give((void **)&wp_err.error);
+ }
+
+ if(mangled)
+ ps_global->mangled_screen = 1;
+
+ free_ldap_result_list(&serv_res);
+ retval = 1;
+ }
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ if(ldapurl)
+ ldap_free_urldesc(ldapurl);
+
+ return(retval);
+}
+#endif /* ENABLE_LDAP */