EXPORTED void strarray_free(strarray_t *sa) { if (!sa) return; strarray_fini(sa); free(sa); }
int sieve_execute_bytecode(sieve_execute_t *exe, sieve_interp_t *interp, void *script_context, void *message_context) { action_list_t *actions = NULL; notify_list_t *notify_list = NULL; /* notify_action_t *notify_action;*/ action_t lastaction = -1; int ret; char actions_string[ACTIONS_STRING_LEN] = ""; const char *errmsg = NULL; strarray_t imapflags = STRARRAY_INITIALIZER; if (!interp) return SIEVE_FAIL; if (interp->notify) { notify_list = new_notify_list(); if (notify_list == NULL) { return do_sieve_error(SIEVE_NOMEM, interp, script_context, message_context, &imapflags, actions, notify_list, lastaction, 0, actions_string, errmsg); } } actions = new_action_list(); if (actions == NULL) { ret = do_sieve_error(SIEVE_NOMEM, interp, script_context, message_context, &imapflags, actions, notify_list, lastaction, 0, actions_string, errmsg); } else { ret = sieve_eval_bc(exe, 0, interp, script_context, message_context, &imapflags, actions, notify_list, &errmsg); if (ret < 0) { ret = do_sieve_error(SIEVE_RUN_ERROR, interp, script_context, message_context, &imapflags, actions, notify_list, lastaction, 0, actions_string, errmsg); } else { ret = do_action_list(interp, script_context, message_context, &imapflags, actions, notify_list, actions_string, errmsg); } } strarray_fini(&imapflags); return ret; }
EXPORTED void varlist_fini(variable_list_t *vl) { if (!vl) { return; } if (vl->name) { free(vl->name); vl->name = NULL; } if (vl->var) { strarray_fini(vl->var); vl->var = NULL; } varlist_free(vl->next); vl->next = NULL; }
EXPORTED int run_command(const char *argv0, ...) { va_list va; const char *p; strarray_t argv = STRARRAY_INITIALIZER; pid_t pid; int r = 0; strarray_append(&argv, argv0); va_start(va, argv0); while ((p = va_arg(va, const char *))) strarray_append(&argv, p); va_end(va); pid = fork(); if (pid < 0) { syslog(LOG_ERR, "Failed to fork: %m"); r = IMAP_SYS_ERROR; goto out; } if (!pid) { /* in child */ r = execv(argv0, argv.data); syslog(LOG_ERR, "Failed to execute %s: %m", argv0); exit(1); } else { /* in parent */ r = wait_for_child(argv0, pid); } out: strarray_fini(&argv); return r; }
int main(int argc, char **argv) { int opt; char *alt_config = NULL; int r = IMAP_NOTFOUND; strarray_t mboxnames = STRARRAY_INITIALIZER; const char *query = NULL; int background = 1; const char *channel = "squatter"; const char *synclogfile = NULL; int init_flags = CYRUSINIT_PERROR; int multi_folder = 0; int user_mode = 0; int compact_flags = 0; const char *fromfile = NULL; strarray_t *srctiers = NULL; const char *desttier = NULL; enum { UNKNOWN, INDEXER, INDEXFROM, SEARCH, ROLLING, SYNCLOG, START_DAEMON, STOP_DAEMON, RUN_DAEMON, COMPACT } mode = UNKNOWN; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EC_USAGE); } setbuf(stdout, NULL); while ((opt = getopt(argc, argv, "C:I:N:RAXT:S:Fc:de:f:mn:rsiavz:t:ou")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 'A': compact_flags |= SEARCH_COMPACT_AUDIT; break; case 'F': compact_flags |= SEARCH_COMPACT_FILTER; break; case 'X': compact_flags |= SEARCH_COMPACT_REINDEX; break; case 'N': name_starts_from = optarg; break; case 'I': /* indexer, using specified mbox/uids in file */ if (mode != UNKNOWN && mode != INDEXFROM) usage(argv[0]); fromfile = optarg; mode = INDEXFROM; break; case 'R': /* rolling indexer */ if (mode != UNKNOWN) usage(argv[0]); mode = ROLLING; incremental_mode = 1; /* always incremental if rolling */ break; case 'S': /* sleep time in seconds */ sleepmicroseconds = (atof(optarg) * 1000000); break; case 'T': /* temporary root directory for search */ temp_root_dir = optarg; break; /* This option is deliberately undocumented, for testing only */ case 'c': /* daemon control mode */ if (mode != UNKNOWN) usage(argv[0]); if (!strcmp(optarg, "start")) mode = START_DAEMON; else if (!strcmp(optarg, "stop")) mode = STOP_DAEMON; else if (!strcmp(optarg, "run")) mode = RUN_DAEMON; else usage(argv[0]); break; case 'd': /* foreground (with -R) */ background = 0; break; /* This option is deliberately undocumented, for testing only */ case 'e': /* add a search term */ if (mode != UNKNOWN && mode != SEARCH) usage(argv[0]); query = optarg; mode = SEARCH; break; case 'f': /* alternate synclogfile used in SYNCLOG mode */ synclogfile = optarg; mode = SYNCLOG; break; /* This option is deliberately undocumented, for testing only */ case 'm': /* multi-folder in SEARCH mode */ if (mode != UNKNOWN && mode != SEARCH) usage(argv[0]); multi_folder = 1; mode = SEARCH; break; case 'n': /* sync channel name (with -R) */ channel = optarg; break; case 'o': /* copy one DB rather than compressing */ compact_flags |= SEARCH_COMPACT_COPYONE; break; case 'v': /* verbose */ verbose++; break; case 'r': /* recurse */ if (mode != UNKNOWN && mode != INDEXER) usage(argv[0]); recursive_flag = 1; mode = INDEXER; break; case 'i': /* incremental mode */ incremental_mode = 1; break; case 'a': /* use /squat annotation */ if (mode != UNKNOWN && mode != INDEXER) usage(argv[0]); annotation_flag = 1; mode = INDEXER; break; case 'z': if (mode != UNKNOWN && mode != COMPACT) usage(argv[0]); desttier = optarg; mode = COMPACT; break; case 't': if (mode != UNKNOWN && mode != COMPACT) usage(argv[0]); srctiers = strarray_split(optarg, ",", 0); mode = COMPACT; break; case 'u': user_mode = 1; break; default: usage("squatter"); } } compact_flags |= SEARCH_VERBOSE(verbose); if (mode == UNKNOWN) mode = INDEXER; /* fork and close fds if required */ if (mode == ROLLING && background) { become_daemon(); init_flags &= ~CYRUSINIT_PERROR; } if (mode == COMPACT && (!desttier || !srctiers)) { /* need both src and dest for compact */ usage("squatter"); } cyrus_init(alt_config, "squatter", init_flags, CONFIG_NEED_PARTITION_DATA); /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace(&squat_namespace, 1)) != 0) { fatal(error_message(r), EC_CONFIG); } annotate_init(NULL, NULL); annotatemore_open(); mboxlist_init(0); mboxlist_open(NULL); if (mode == ROLLING || mode == SYNCLOG) { signals_set_shutdown(&shut_down); signals_add_handlers(0); } switch (mode) { case UNKNOWN: break; case INDEXER: /* -r requires at least one mailbox */ if (recursive_flag && optind == argc) usage(argv[0]); expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); syslog(LOG_NOTICE, "indexing mailboxes"); r = do_indexer(&mboxnames); syslog(LOG_NOTICE, "done indexing mailboxes"); break; case INDEXFROM: syslog(LOG_NOTICE, "indexing messages"); r = do_indexfrom(fromfile); syslog(LOG_NOTICE, "done indexing messages"); break; case SEARCH: if (recursive_flag && optind == argc) usage(argv[0]); expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); r = do_search(query, !multi_folder, &mboxnames); break; case ROLLING: do_rolling(channel); /* never returns */ break; case SYNCLOG: r = do_synclogfile(synclogfile); break; case START_DAEMON: if (optind != argc) usage("squatter"); search_start_daemon(verbose); break; case STOP_DAEMON: if (optind != argc) usage("squatter"); search_stop_daemon(verbose); break; case RUN_DAEMON: if (optind != argc) usage("squatter"); do_run_daemon(); break; case COMPACT: if (recursive_flag && optind == argc) usage(argv[0]); expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); r = do_compact(&mboxnames, srctiers, desttier, compact_flags); break; } strarray_fini(&mboxnames); shut_down(r ? EC_TEMPFAIL : 0); }
static int do_notify(void) { struct sockaddr_un sun_data; socklen_t sunlen = sizeof(sun_data); char buf[NOTIFY_MAXSIZE+1], *cp, *tail; int r, i; char *method, *class, *priority, *user, *mailbox, *message; strarray_t options = STRARRAY_INITIALIZER; long nopt; char *reply; char *fname; notifymethod_t *nmethod; while (1) { method = class = priority = user = mailbox = message = reply = NULL; nopt = 0; if (signals_poll() == SIGHUP) { /* caught a SIGHUP, return */ return 0; } r = recvfrom(soc, buf, NOTIFY_MAXSIZE, 0, (struct sockaddr *) &sun_data, &sunlen); if (r == -1) { return (errno); } buf[r] = '\0'; tail = buf + r - 1; /* * parse request of the form: * * method NUL class NUL priority NUL user NUL mailbox NUL * nopt NUL N(option NUL) message NUL */ method = (cp = buf); if (cp) class = (cp = fetch_arg(cp, tail)); if (cp) priority = (cp = fetch_arg(cp, tail)); if (cp) user = (cp = fetch_arg(cp, tail)); if (cp) mailbox = (cp = fetch_arg(cp, tail)); if (cp) cp = fetch_arg(cp, tail); /* skip to nopt */ if (cp) nopt = strtol(cp, NULL, 10); if (nopt < 0 || errno == ERANGE) cp = NULL; for (i = 0; cp && i < nopt; i++) strarray_appendm(&options, cp = fetch_arg(cp, tail)); if (cp) message = (cp = fetch_arg(cp, tail)); if (cp) fname = (cp = fetch_arg(cp, tail)); if (!message) { syslog(LOG_ERR, "malformed notify request"); strarray_fini(&options); return 0; } if (!*method) nmethod = default_method; else { nmethod = methods; while (nmethod->name) { if (!strcasecmp(nmethod->name, method)) break; nmethod++; } } syslog(LOG_DEBUG, "do_notify using method '%s'", nmethod->name ? nmethod->name: "unknown"); if (nmethod->name) { reply = nmethod->notify(class, priority, user, mailbox, nopt, options.data, message, fname); } #if 0 /* we don't care about responses right now */ else {
EXPORTED int command_popen(struct command **cmdp, const char *mode, const char *cwd, const char *argv0, ...) { va_list va; const char *p; strarray_t argv = STRARRAY_INITIALIZER; pid_t pid; int r = 0; struct command *cmd; int do_stdin = (strchr(mode, 'w') != NULL); int do_stdout = (strchr(mode, 'r') != NULL); int stdin_pipe[2] = { -1, -1 }; int stdout_pipe[2] = { -1, -1 }; strarray_append(&argv, argv0); va_start(va, argv0); while ((p = va_arg(va, const char *))) strarray_append(&argv, p); va_end(va); if (do_stdin) { r = pipe(stdin_pipe); if (r) { syslog(LOG_ERR, "Failed to pipe(): %m"); r = IMAP_SYS_ERROR; goto out; } } if (do_stdout) { r = pipe(stdout_pipe); if (r) { syslog(LOG_ERR, "Failed to pipe(): %m"); r = IMAP_SYS_ERROR; goto out; } } pid = fork(); if (pid < 0) { syslog(LOG_ERR, "Failed to fork: %m"); r = IMAP_SYS_ERROR; goto out; } if (!pid) { /* in child */ if (do_stdin) { close(stdin_pipe[PIPE_WRITE]); dup2(stdin_pipe[PIPE_READ], STDIN_FILENO); close(stdin_pipe[PIPE_READ]); } if (do_stdout) { close(stdout_pipe[PIPE_READ]); dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO); close(stdout_pipe[PIPE_WRITE]); } if (cwd) { r = chdir(cwd); if (r) syslog(LOG_ERR, "Failed to chdir(%s): %m", cwd); } r = execv(argv0, argv.data); syslog(LOG_ERR, "Failed to execute %s: %m", argv0); exit(1); } /* in parent */ cmd = xzmalloc(sizeof(struct command)); cmd->argv0 = xstrdup(argv0); cmd->pid = pid; if (do_stdin) cmd->stdin_prot = prot_new(stdin_pipe[PIPE_WRITE], /*write*/1); if (do_stdout) cmd->stdout_prot = prot_new(stdout_pipe[PIPE_READ], /*write*/0); *cmdp = cmd; out: if (stdin_pipe[PIPE_READ] >= 0) close(stdin_pipe[PIPE_READ]); if (stdout_pipe[PIPE_WRITE] >= 0) close(stdout_pipe[PIPE_WRITE]); if (r) { if (stdin_pipe[PIPE_WRITE] >= 0) close(stdin_pipe[PIPE_WRITE]); if (stdout_pipe[PIPE_READ] >= 0) close(stdout_pipe[PIPE_READ]); } strarray_fini(&argv); return r; }
static int do_action_list(sieve_interp_t *interp, void *script_context, void *message_context, strarray_t *imapflags, action_list_t *actions, notify_list_t *notify_list, /* notify_action_t *notify_action,*/ char *actions_string, const char *errmsg) { action_list_t *a; action_t lastaction = -1; int ret = 0; int implicit_keep = 1; strcpy(actions_string,"Action(s) taken:\n"); /* now perform actions attached to m */ a = actions; while (a != NULL) { lastaction = a->a; errmsg = NULL; implicit_keep = implicit_keep && !a->cancel_keep; switch (a->a) { case ACTION_REJECT: if (!interp->reject) return SIEVE_INTERNAL_ERROR; ret = interp->reject(&a->u.rej, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = xstrdup(a->u.rej.msg); if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Rejected with: %s\n", a->u.rej.msg); break; case ACTION_FILEINTO: if (!interp->fileinto) return SIEVE_INTERNAL_ERROR; ret = interp->fileinto(&a->u.fil, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = xstrdup(a->u.fil.mailbox); if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Filed into: %s\n",a->u.fil.mailbox); break; case ACTION_KEEP: if (!interp->keep) return SIEVE_INTERNAL_ERROR; ret = interp->keep(&a->u.keep, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = NULL; if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Kept\n"); break; case ACTION_REDIRECT: if (!interp->redirect) return SIEVE_INTERNAL_ERROR; ret = interp->redirect(&a->u.red, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = xstrdup(a->u.red.addr); if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Redirected to %s\n", a->u.red.addr); break; case ACTION_DISCARD: if (interp->discard) /* discard is optional */ ret = interp->discard(NULL, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = NULL; if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Discarded\n"); break; case ACTION_VACATION: { if (!interp->vacation) return SIEVE_INTERNAL_ERROR; /* first, let's figure out if we should respond to this */ ret = interp->vacation->autorespond(&a->u.vac.autoresp, interp->interp_context, script_context, message_context, &errmsg); free(interp->lastitem); interp->lastitem = NULL; if (ret == SIEVE_OK) { /* send the response */ ret = interp->vacation->send_response(&a->u.vac.send, interp->interp_context, script_context, message_context, &errmsg); if (ret == SIEVE_OK) snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Sent vacation reply\n"); } else if (ret == SIEVE_DONE) { snprintf(actions_string+strlen(actions_string), ACTIONS_STRING_LEN-strlen(actions_string), "Vacation reply suppressed\n"); ret = SIEVE_OK; } break; } case ACTION_SETFLAG: strarray_fini(imapflags); break; case ACTION_ADDFLAG: strarray_add_case(imapflags, a->u.fla.flag); free(interp->lastitem); interp->lastitem = xstrdup(a->u.fla.flag); break; case ACTION_REMOVEFLAG: strarray_remove_all_case(imapflags, a->u.fla.flag); free(interp->lastitem); interp->lastitem = xstrdup(a->u.fla.flag); break; case ACTION_MARK: { int n = interp->markflags->count; ret = SIEVE_OK; while (n) { strarray_add_case(imapflags, interp->markflags->data[--n]); } free(interp->lastitem); interp->lastitem = NULL; break; } case ACTION_UNMARK: { int n = interp->markflags->count; ret = SIEVE_OK; while (n) { strarray_remove_all_case(imapflags, interp->markflags->data[--n]); } free(interp->lastitem); interp->lastitem = NULL; break; } case ACTION_NONE: break; default: ret = SIEVE_INTERNAL_ERROR; break; } a = a->next; if (ret != SIEVE_OK) { /* uh oh! better bail! */ break; } } return do_sieve_error(ret, interp, script_context, message_context, imapflags, actions, notify_list, lastaction, implicit_keep, actions_string, errmsg); }
static void myfreestate(struct auth_state *auth_state) { strarray_fini(&auth_state->groups); free(auth_state); }
int main(int argc, char **argv) { int opt, i, r; int dousers = 0; int rflag = 0; int mflag = 0; int fflag = 0; int xflag = 0; char buf[MAX_MAILBOX_PATH+1]; strarray_t discovered = STRARRAY_INITIALIZER; char *alt_config = NULL; char *start_part = NULL; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EC_USAGE); } construct_hash_table(&unqid_table, 2047, 1); while ((opt = getopt(argc, argv, "C:kp:rmfsxgGqRUMoOnV:u")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 'p': start_part = optarg; break; case 'r': rflag = 1; break; case 'u': dousers = 1; break; case 'm': mflag = 1; break; case 'n': reconstruct_flags &= ~RECONSTRUCT_MAKE_CHANGES; break; case 'g': fprintf(stderr, "reconstruct: deprecated option -g ignored\n"); break; case 'G': reconstruct_flags |= RECONSTRUCT_ALWAYS_PARSE; break; case 'f': fflag = 1; break; case 'x': xflag = 1; break; case 'k': fprintf(stderr, "reconstruct: deprecated option -k ignored\n"); break; case 's': reconstruct_flags &= ~RECONSTRUCT_DO_STAT; break; case 'q': reconstruct_flags |= RECONSTRUCT_QUIET; break; case 'R': reconstruct_flags |= RECONSTRUCT_GUID_REWRITE; break; case 'U': reconstruct_flags |= RECONSTRUCT_GUID_UNLINK; break; case 'o': reconstruct_flags |= RECONSTRUCT_IGNORE_ODDFILES; break; case 'O': reconstruct_flags |= RECONSTRUCT_REMOVE_ODDFILES; break; case 'M': reconstruct_flags |= RECONSTRUCT_PREFER_MBOXLIST; break; case 'V': if (!strcasecmp(optarg, "max")) setversion = MAILBOX_MINOR_VERSION; else setversion = atoi(optarg); break; default: usage(); } } cyrus_init(alt_config, "reconstruct", 0, CONFIG_NEED_PARTITION_DATA); global_sasl_init(1,0,NULL); /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace(&recon_namespace, 1)) != 0) { syslog(LOG_ERR, "%s", error_message(r)); fatal(error_message(r), EC_CONFIG); } sync_log_init(); if (mflag) { if (rflag || fflag || optind != argc) { cyrus_done(); usage(); } do_mboxlist(); } mboxlist_init(0); mboxlist_open(NULL); quotadb_init(0); quotadb_open(NULL); #ifdef WITH_DAV caldav_init(); carddav_init(); webdav_init(); #endif /* Deal with nonexistent mailboxes */ if (start_part) { /* We were handed a mailbox that does not exist currently */ if(optind == argc) { fprintf(stderr, "When using -p, you must specify a mailbox to attempt to reconstruct."); exit(EC_USAGE); } /* do any of the mailboxes exist in mboxlist already? */ /* Do they look like mailboxes? */ for (i = optind; i < argc; i++) { if (strchr(argv[i],'%') || strchr(argv[i],'*')) { fprintf(stderr, "Using wildcards with -p is not supported.\n"); exit(EC_USAGE); } /* Translate mailboxname */ char *intname = mboxname_from_external(argv[i], &recon_namespace, NULL); /* Does it exist */ do { r = mboxlist_lookup(intname, NULL, NULL); } while (r == IMAP_AGAIN); if (r != IMAP_MAILBOX_NONEXISTENT) { fprintf(stderr, "Mailbox %s already exists. Cannot specify -p.\n", argv[i]); exit(EC_USAGE); } free(intname); } /* None of them exist. Create them. */ for (i = optind; i < argc; i++) { /* Translate mailboxname */ char *intname = mboxname_from_external(argv[i], &recon_namespace, NULL); /* don't notify mailbox creation here */ r = mboxlist_createmailbox(intname, 0, start_part, 1, "cyrus", NULL, 0, 0, !xflag, 0, NULL); if (r) { fprintf(stderr, "could not create %s\n", argv[i]); } free(intname); } } /* Normal Operation */ if (optind == argc) { if (rflag || dousers) { fprintf(stderr, "please specify a mailbox to recurse from\n"); cyrus_done(); exit(EC_USAGE); } assert(!rflag); strlcpy(buf, "*", sizeof(buf)); mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, NULL); } for (i = optind; i < argc; i++) { if (dousers) { mboxlist_usermboxtree(argv[i], do_reconstruct_p, NULL, MBOXTREE_TOMBSTONES|MBOXTREE_DELETED); continue; } char *domain = NULL; /* save domain */ if (config_virtdomains) domain = strchr(argv[i], '@'); strlcpy(buf, argv[i], sizeof(buf)); /* reconstruct the first mailbox/pattern */ mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, fflag ? &discovered : NULL); if (rflag) { /* build a pattern for submailboxes */ char *p = strchr(buf, '@'); if (p) *p = '\0'; strlcat(buf, ".*", sizeof(buf)); /* append the domain */ if (domain) strlcat(buf, domain, sizeof(buf)); /* reconstruct the submailboxes */ mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, fflag ? &discovered : NULL); } } /* examine our list to see if we discovered anything */ while (discovered.count) { char *name = strarray_shift(&discovered); int r = 0; /* create p (database only) and reconstruct it */ /* partition is defined by the parent mailbox */ /* don't notify mailbox creation here */ r = mboxlist_createmailbox(name, 0, NULL, 1, "cyrus", NULL, 0, 0, !xflag, 0, NULL); if (r) { fprintf(stderr, "createmailbox %s: %s\n", name, error_message(r)); } else { mboxlist_findone(&recon_namespace, name, 1, 0, 0, do_reconstruct, &discovered); } /* may have added more things into our list */ free(name); } free_hash_table(&unqid_table, free); sync_log_done(); mboxlist_close(); mboxlist_done(); quotadb_close(); quotadb_done(); partlist_local_done(); #ifdef WITH_DAV webdav_done(); carddav_done(); caldav_done(); #endif cyrus_done(); strarray_fini(&discovered); return 0; }