static int cmd_show_mailboxes(struct backup *backup, const struct cyrbu_cmd_options *options) { struct backup_mailbox *mailbox = NULL; struct backup_mailbox_message *record = NULL; int i; for (i = 0; i < strarray_size(options->argv); i++) { char ts_deleted[32] = ""; const char *arg = strarray_nth(options->argv, i); /* argument could be a uniqueid */ mailbox = backup_get_mailbox_by_uniqueid(backup, arg, 1); /* or it could be an mboxname */ if (!mailbox) { mbname_t *mbname = mbname_from_intname(arg); if (!mbname) continue; mailbox = backup_get_mailbox_by_name(backup, mbname, 1); mbname_free(&mbname); } /* or it could be junk */ if (!mailbox) continue; fprintf(stdout, "mboxname: %s\n", mailbox->mboxname); fprintf(stdout, "uniqueid: %s\n", mailbox->uniqueid); if (mailbox->deleted) { strftime(ts_deleted, sizeof(ts_deleted), "%F %T", localtime(&mailbox->deleted)); fprintf(stdout, "deleted: %s\n", ts_deleted); } fprintf(stdout, "messages:\n"); fprintf(stdout, " uid expunged time guid\n"); for (record = mailbox->records->head; record; record = record->next) { char ts_expunged[32] = " "; if (record->expunged) strftime(ts_expunged, sizeof(ts_expunged), "%F %T", localtime(&record->expunged)); fprintf(stdout, "%10d %s %s\n", record->uid, ts_expunged, message_guid_encode(&record->guid)); } fprintf(stdout, "\n"); backup_mailbox_free(&mailbox); } return 0; }
static int _index_expunge(struct backup *backup, struct dlist *dl, time_t ts, off_t dl_offset) { syslog(LOG_DEBUG, "indexing EXPUNGE at " OFF_T_FMT "...\n", dl_offset); const char *mboxname; const char *uniqueid; struct dlist *uidl; struct dlist *di; struct backup_mailbox *mailbox = NULL; int r = 0; if (!dlist_getatom(dl, "MBOXNAME", &mboxname)) return IMAP_PROTOCOL_BAD_PARAMETERS; if (!dlist_getatom(dl, "UNIQUEID", &uniqueid)) return IMAP_PROTOCOL_BAD_PARAMETERS; if (!dlist_getlist(dl, "UID", &uidl)) return IMAP_PROTOCOL_BAD_PARAMETERS; mbname_t *mbname = mbname_from_intname(mboxname); mailbox = backup_get_mailbox_by_name(backup, mbname, 0); mbname_free(&mbname); if (!mailbox) return IMAP_MAILBOX_NONEXISTENT; /* verify that uniqueid matches */ if (strcmp(mailbox->uniqueid, uniqueid) != 0) { syslog(LOG_ERR, "%s: uniqueid mismatch for %s: %s on wire, %s in index", __func__, mboxname, uniqueid, mailbox->uniqueid); r = IMAP_PROTOCOL_BAD_PARAMETERS; } for (di = uidl->head; di && !r; di = di->next) { struct sqldb_bindval bval[] = { { ":mailbox_id", SQLITE_INTEGER, { .i = mailbox->id } },
int main(int argc, char **argv) { save_argv0(argv[0]); struct cyrbu_cmd_options options = {0}; enum cyrbu_mode mode = CYRBU_MODE_UNSPECIFIED; enum cyrbu_cmd cmd = CYRBU_CMD_UNSPECIFIED; const char *alt_config = NULL; const char *backup_name = NULL; const char *command = NULL; const char *subcommand = NULL; struct backup *backup = NULL; mbname_t *mbname = NULL; int i, opt, r = 0; while ((opt = getopt(argc, argv, "C:fmuv")) != EOF) { switch (opt) { case 'C': alt_config = optarg; break; case 'f': if (mode != CYRBU_MODE_UNSPECIFIED) usage(); mode = CYRBU_MODE_FILENAME; break; case 'm': if (mode != CYRBU_MODE_UNSPECIFIED) usage(); mode = CYRBU_MODE_MBOXNAME; break; case 'u': if (mode != CYRBU_MODE_UNSPECIFIED) usage(); mode = CYRBU_MODE_USERNAME; break; case 'v': options.verbose++; break; default: usage(); break; } } /* default mode is username */ if (mode == CYRBU_MODE_UNSPECIFIED) mode = CYRBU_MODE_USERNAME; /* get the backup name */ if (optind == argc) usage(); backup_name = argv[optind++]; /* get the command */ if (optind == argc) usage(); command = argv[optind++]; /* get the subcommand */ if (optind == argc) usage(); subcommand = argv[optind++]; /* parse the command and subcommand */ cmd = parse_cmd_string(command, subcommand); /* check remaining arguments based on command */ switch (cmd) { case CYRBU_CMD_LIST_ALL: case CYRBU_CMD_LIST_CHUNKS: case CYRBU_CMD_LIST_MAILBOXES: case CYRBU_CMD_LIST_MESSAGES: /* these want no more arguments */ if (optind != argc) usage(); break; case CYRBU_CMD_SHOW_CHUNKS: case CYRBU_CMD_SHOW_MAILBOXES: case CYRBU_CMD_SHOW_MESSAGES: /* these need at least one more argument */ if (optind == argc) usage(); break; case CYRBU_CMD_DUMP_CHUNK: case CYRBU_CMD_DUMP_MAILBOX: case CYRBU_CMD_DUMP_MESSAGE: /* these need exactly one more argument */ if (argc - optind != 1) usage(); break; default: usage(); break; } /* build a nice args list */ options.argv = strarray_new(); for (i = optind; i < argc; i++) { strarray_add(options.argv, argv[i]); } // FIXME finish parsing options cyrus_init(alt_config, "cyr_backup", 0, 0); /* open backup */ switch (mode) { case CYRBU_MODE_FILENAME: r = backup_open_paths(&backup, backup_name, NULL, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); break; case CYRBU_MODE_MBOXNAME: mbname = mbname_from_intname(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); break; case CYRBU_MODE_USERNAME: mbname = mbname_from_userid(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); break; default: usage(); break; } /* run command */ if (!r && cmd_func[cmd]) r = cmd_func[cmd](backup, &options); if (r) fprintf(stderr, "%s: %s\n", backup_name, error_message(r)); /* close backup */ if (backup) backup_close(&backup); /* clean up and exit */ backup_cleanup_staging_path(); cyrus_done(); strarray_free(options.argv); exit(r ? EC_TEMPFAIL : EC_OK); }
static int restore_add_object(const char *object_name, const struct restore_options *options, struct backup *backup, struct backup_mailbox_list *mailbox_list, struct sync_folder_list *reserve_folder_list, struct sync_reserve_list *reserve_list) { struct backup_mailbox *mailbox = NULL; struct backup_message *message = NULL; struct message_guid tmp_guid; size_t len; int r; /* try to work out what we're restoring */ len = strlen(object_name); if (len == 24 && strspn(object_name, HEX_DIGITS) == len) { /* looks like a non-libuuid uniqueid */ mailbox = backup_get_mailbox_by_uniqueid(backup, object_name, BACKUP_MAILBOX_ALL_RECORDS); } else if (len == 36 && strspn(object_name, "-" HEX_DIGITS) == len) { /* looks like a libuuid uniqueid */ mailbox = backup_get_mailbox_by_uniqueid(backup, object_name, BACKUP_MAILBOX_ALL_RECORDS); } else if (message_guid_decode(&tmp_guid, object_name)) { /* looks like it's a message guid */ message = backup_get_message(backup, &tmp_guid); } else if (strchr(object_name, '.')) { /* has a dot, might be an mboxname */ mbname_t *mbname = mbname_from_intname(object_name); mailbox = backup_get_mailbox_by_name(backup, mbname, BACKUP_MAILBOX_ALL_RECORDS); mbname_free(&mbname); } else { /* not sure what it is, guess mboxname? */ mbname_t *mbname = mbname_from_intname(object_name); mailbox = backup_get_mailbox_by_name(backup, mbname, BACKUP_MAILBOX_ALL_RECORDS); mbname_free(&mbname); } /* add it to the restore lists */ if (mailbox) { r = restore_add_mailbox(mailbox, options, mailbox_list, reserve_folder_list, reserve_list); if (!r && options->do_submailboxes) { char prefix[MAX_MAILBOX_NAME + 1]; int len; len = snprintf(prefix, sizeof(prefix), "%s.", mailbox->mboxname); /* can only be submailboxes if parent's name is short enough... */ if (len < MAX_MAILBOX_NAME) { struct submailbox_rock rock = { prefix, strlen(prefix), options, mailbox_list, reserve_folder_list, reserve_list, }; r = backup_mailbox_foreach(backup, 0, BACKUP_MAILBOX_ALL_RECORDS, submailbox_cb, &rock); } } backup_mailbox_free(&mailbox); } else if (message) { struct backup_mailbox_list *mailboxes = NULL; if (!options->override_mboxname) mailboxes = backup_get_mailboxes_by_message(backup, message, BACKUP_MAILBOX_MATCH_RECORDS); r = restore_add_message(message, mailboxes, options, mailbox_list, reserve_folder_list, reserve_list); if (mailboxes) { backup_mailbox_list_empty(mailboxes); free(mailboxes); } backup_message_free(&message); } else { r = IMAP_MAILBOX_NONEXISTENT; } return r; }
static void apply_mailbox_options(struct backup_mailbox *mailbox, const struct restore_options *options) { if (options->override_mboxname) { if (options->verbose) { fprintf(stderr, "%s: overriding mboxname with %s\n", mailbox->mboxname, options->override_mboxname); } if (mailbox->mboxname) free(mailbox->mboxname); mailbox->mboxname = xstrdup(options->override_mboxname); } if (options->override_partition) { if (options->verbose) { fprintf(stderr, "%s: overriding partition with %s (was %s)\n", mailbox->mboxname, options->override_partition, mailbox->partition); } if (mailbox->partition) free(mailbox->partition); mailbox->partition = xstrdup(options->override_partition); } if (options->override_acl) { if (options->verbose) { fprintf(stderr, "%s: overriding acl with '%s' (was '%s')\n", mailbox->mboxname, options->override_acl, mailbox->acl); } if (mailbox->acl) free(mailbox->acl); /* treat empty override string as no ACL, resulting in the mailbox * being restored with the default ACL for its owner. */ if (*options->override_acl) { mailbox->acl = xstrdup(options->override_acl); } else { mailbox->acl = NULL; } } if (!options->keep_uidvalidity) { if (options->verbose) { fprintf(stderr, "%s: not trying to keep uidvalidity\n", mailbox->mboxname); } if (mailbox->uniqueid) free(mailbox->uniqueid); mailbox->uniqueid = NULL; mailbox->highestmodseq = 0; mailbox->uidvalidity = 0; } else if (options->verbose) { fprintf(stderr, "%s: trying to keep uidvalidity(%u), " "uniqueid(%s), highestmodseq(" MODSEQ_FMT ")\n", mailbox->mboxname, mailbox->uidvalidity, mailbox->uniqueid, mailbox->highestmodseq); } if (mailbox->mboxname && options->trim_deletedprefix) { mbname_t *mbname = mbname_from_intname(mailbox->mboxname); if (mbname_isdeleted(mbname)) { char *freeme = mailbox->mboxname; mbname_set_isdeleted(mbname, 0); mailbox->mboxname = xstrdup(mbname_intname(mbname)); if (options->verbose) { fprintf(stderr, "%s: removing deletedprefix (was %s)\n", mailbox->mboxname, freeme); } free(freeme); } mbname_free(&mbname); } if (options->expunged_mode != RESTORE_EXPUNGED_OKAY) { struct backup_mailbox_message *iter, *tmp, *next; next = mailbox->records->head; while ((iter = next)) { next = iter->next; tmp = NULL; switch (options->expunged_mode) { case RESTORE_EXPUNGED_EXCLUDE: if (iter->expunged) { tmp = backup_mailbox_message_list_remove(mailbox->records, iter); if (options->verbose) { fprintf(stderr, "%s: excluding expunged message: %s\n", mailbox->mboxname, message_guid_encode(&iter->guid)); } } break; case RESTORE_EXPUNGED_ONLY: if (!iter->expunged) { tmp = backup_mailbox_message_list_remove(mailbox->records, iter); if (options->verbose) { fprintf(stderr, "%s: excluding unexpunged message: %s\n", mailbox->mboxname, message_guid_encode(&iter->guid)); } } break; default: break; } if (tmp) backup_mailbox_message_free(&tmp); } } }
int main(int argc, char **argv) { save_argv0(argv[0]); const char *alt_config = NULL; const char *input_file = NULL; const char *backup_name = NULL; const char *servername = NULL; enum restore_mode mode = RESTORE_MODE_UNSPECIFIED; int local_only = 0; int wait = 0; int do_nothing = 0; int do_all_mailboxes = 0; struct restore_options options = {0}; options.expunged_mode = RESTORE_EXPUNGED_OKAY; options.trim_deletedprefix = 1; struct backup *backup = NULL; mbname_t *mbname = NULL; struct backup_mailbox_list *mailbox_list = NULL; struct sync_folder_list *reserve_folder_list = NULL; struct sync_reserve_list *reserve_list = NULL; struct buf tagbuf = BUF_INITIALIZER; struct backend *backend = NULL; struct dlist *upload = NULL; int opt, r; while ((opt = getopt(argc, argv, ":A:C:DF:LM:P:UXaf:m:nru:vw:xz")) != EOF) { switch (opt) { case 'A': if (options.keep_uidvalidity) usage(); options.override_acl = optarg; break; case 'C': alt_config = optarg; break; case 'D': /* XXX does this clash with keep_uidvalidity? */ options.trim_deletedprefix = 0; break; case 'F': if (do_all_mailboxes) usage(); input_file = optarg; break; case 'L': local_only = 1; break; case 'M': if (options.keep_uidvalidity) usage(); options.override_mboxname = optarg; break; case 'P': if (options.keep_uidvalidity) usage(); options.override_partition = optarg; break; case 'U': if (options.override_acl || options.override_mboxname || options.override_partition) usage(); options.keep_uidvalidity = 1; break; case 'X': if (options.expunged_mode != RESTORE_EXPUNGED_OKAY) usage(); options.expunged_mode = RESTORE_EXPUNGED_EXCLUDE; break; case 'a': if (input_file) usage(); do_all_mailboxes = 1; break; case 'f': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_FILENAME; backup_name = optarg; break; case 'm': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_MBOXNAME; backup_name = optarg; break; case 'n': do_nothing = 1; break; case 'r': options.do_submailboxes = 1; break; case 'u': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_USERNAME; backup_name = optarg; break; case 'v': options.verbose++; break; case 'w': wait = atoi(optarg); if (wait < 0) usage(); break; case 'x': if (options.expunged_mode != RESTORE_EXPUNGED_OKAY) usage(); options.expunged_mode = RESTORE_EXPUNGED_ONLY; break; case 'z': options.require_compression = 1; break; case ':': if (optopt == 'A') options.override_acl = ""; else usage(); break; default: usage(); break; } } /* we need a server name */ if (optind == argc) usage(); servername = argv[optind++]; /* we need a source of backup data */ if (mode == RESTORE_MODE_UNSPECIFIED) { if (optind == argc) usage(); backup_name = argv[optind++]; mode = RESTORE_MODE_USERNAME; } /* we need either an input file or some objects to restore */ if (!do_all_mailboxes && !input_file && optind == argc) usage(); /* and we can't have both because i said */ if ((input_file || do_all_mailboxes) && optind < argc) usage(); /* okay, arguments seem sane, we are go */ cyrus_init(alt_config, "restore", 0, 0); /* load the SASL plugins */ global_sasl_init(1, 0, mysasl_cb); /* wait here for gdb attach */ if (wait) { fprintf(stderr, "Waiting for %d seconds for gdb attach...\n", wait); sleep(wait); } /* open backup */ switch (mode) { case RESTORE_MODE_FILENAME: r = backup_open_paths(&backup, backup_name, NULL, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); break; case RESTORE_MODE_MBOXNAME: mbname = mbname_from_intname(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); mbname_free(&mbname); break; case RESTORE_MODE_USERNAME: mbname = mbname_from_userid(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); mbname_free(&mbname); break; default: usage(); break; } if (r) goto done; /* scan for objects to restore: * mailboxes will have all messages added, modulo expunged_mode * messages will be added individually with appropriate folder */ mailbox_list = xzmalloc(sizeof *mailbox_list); reserve_folder_list = restore_make_reserve_folder_list(backup); reserve_list = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE); if (input_file) { char buf[MAX_MAILBOX_NAME + 2]; // \n\0 size_t len; FILE *f; if (0 != strcmp(input_file, "-")) { f = fopen(input_file, "r"); if (!f) { fprintf(stderr, "fopen %s: %s\n", input_file, strerror(errno)); goto done;// FIXME shut_down? } } else { f = stdin; } while (fgets(buf, sizeof(buf), f)) { len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0'; if (len == 0 || buf[0] == '#') continue; r = restore_add_object(buf, &options, backup, mailbox_list, reserve_folder_list, reserve_list); // FIXME r } fclose(f); } else if (do_all_mailboxes) { struct backup_mailbox_list *all_mailboxes; struct backup_mailbox *mailbox; all_mailboxes = backup_get_mailboxes(backup, 0, BACKUP_MAILBOX_ALL_RECORDS); for (mailbox = all_mailboxes->head; mailbox; mailbox = mailbox->next) { restore_add_mailbox(mailbox, &options, mailbox_list, reserve_folder_list, reserve_list); } backup_mailbox_list_empty(all_mailboxes); free(all_mailboxes); } else { int i; for (i = optind; i < argc; i++) { r = restore_add_object(argv[i], &options, backup, mailbox_list, reserve_folder_list, reserve_list); // FIXME r } } if (do_nothing) { if (options.verbose) fprintf(stderr, "do nothing (-n) specified, exiting now\n"); goto done; } /* connect to destination */ backend = restore_connect(servername, &tagbuf, &options); if (!backend) { // FIXME r = -1; goto done; } /* do the restore */ struct sync_reserve *reserve; for (reserve = reserve_list->head; reserve; reserve = reserve->next) { /* send APPLY RESERVE and parse missing lists */ r = sync_reserve_partition(reserve->part, reserve_folder_list, reserve->list, backend); if (r) goto done; /* send APPLY MESSAGEs */ r = backup_prepare_message_upload(backup, reserve->part, reserve->list, &sync_msgid_lookup, &upload); if (r) goto done; /* upload in small(ish) blocks to avoid timeouts */ while (upload->head) { struct dlist *block = dlist_splice(upload, 1024); sync_send_apply(block, backend->out); r = sync_parse_response("MESSAGE", backend->in, NULL); dlist_unlink_files(block); dlist_free(&block); if (r) goto done; } } /* sync_prepare_dlists needs to upload messages per-mailbox, because * it needs the mailbox to find the filename for the message. but * we have no such limitation, so we can upload messages while * looping over the reserve_list instead, above. * * alternatively, if we do it on a per-mailbox basis then we can limit * the hit on the staging directory to only a mailbox worth of messages * at a time. but we don't have a logical grouping that would make * this coherent */ /* send RESTORE MAILBOXes */ struct backup_mailbox *mailbox; for (mailbox = mailbox_list->head; mailbox; mailbox = mailbox->next) { /* XXX filter the mailbox records based on reserve/missing/upload */ /* XXX does this sensibly handle mailbox objs with empty values? */ struct dlist *dl = backup_mailbox_to_dlist(mailbox); if (!dl) continue; if (local_only) { free(dl->name); dl->name = xstrdup("LOCAL_MAILBOX"); } sync_send_restore(dl, backend->out); r = sync_parse_response("MAILBOX", backend->in, NULL); dlist_free(&dl); if (r) goto done; } done: if (r) fprintf(stderr, "%s: %s\n", backup_name, error_message(r)); /* release lock asap */ if (backup) backup_close(&backup); if (backend) backend_disconnect(backend); if (upload) { dlist_unlink_files(upload); dlist_free(&upload); } if (mailbox_list) { backup_mailbox_list_empty(mailbox_list); free(mailbox_list); } if (reserve_folder_list) sync_folder_list_free(&reserve_folder_list); if (reserve_list) sync_reserve_list_free(&reserve_list); buf_free(&tagbuf); backup_cleanup_staging_path(); cyrus_done(); exit(r ? EX_TEMPFAIL : EX_OK); }