Example #1
0
EXPORTED int command_pclose(struct command **cmdp)
{
    struct command *cmd = (cmdp ? *cmdp : NULL);
    int r;

    if (!cmd) return 0;

    if (cmd->stdin_prot) {
        prot_flush(cmd->stdin_prot);
        close(cmd->stdin_prot->fd);
        prot_free(cmd->stdin_prot);
    }

    if (cmd->stdout_prot) {
        close(cmd->stdout_prot->fd);
        prot_free(cmd->stdout_prot);
    }

    r = wait_for_child(cmd->argv0, cmd->pid);

    free(cmd->argv0);
    free(cmd);
    *cmdp = NULL;

    return r;
}
Example #2
0
void shut_down(int code)
{
    /* free interpreter */
    if (interp) sieve_interp_free(&interp);

    /* close backend connection */
    if (backend) {
	backend_disconnect(backend);
	free(backend);
    }

    /* close mailboxes */
    mboxlist_close();
    mboxlist_done();

    /* cleanup */
    if (sieved_out) {
	prot_flush(sieved_out);
	prot_free(sieved_out);
    }
    if (sieved_in) prot_free(sieved_in);

    if (sieved_logfd != -1) close(sieved_logfd);

#ifdef HAVE_SSL
    tls_shutdown_serverengine();
#endif

    cyrus_done();

    cyrus_reset_stdio();
    
    /* done */
    exit(code);
}
Example #3
0
void backend_disconnect(struct backend *s)
{
    char buf[1024];

    if (!s || s->sock == -1) return;
    
    if (!prot_error(s->in)) {
	if (s->prot->logout_cmd.cmd) {
	    prot_printf(s->out, "%s\r\n", s->prot->logout_cmd.cmd);
	    prot_flush(s->out);

	    for (;;) {
		if (!prot_fgets(buf, sizeof(buf), s->in)) {
		    /* connection closed? */
		    break;
		} else if (s->prot->logout_cmd.unsol &&
			   !strncmp(s->prot->logout_cmd.unsol, buf,
				    strlen(s->prot->logout_cmd.unsol))) {
		    /* unsolicited response */
		    continue;
		} else {
		    /* success/fail response -- don't care either way */
		    break;
		}
	    }
	}
    }

    /* Flush the incoming buffer */
    prot_NONBLOCK(s->in);
    prot_fill(s->in);

#ifdef HAVE_SSL
    /* Free tlsconn */
    if (s->tlsconn) {
	tls_reset_servertls(&s->tlsconn);
	s->tlsconn = NULL;
    }
#endif /* HAVE_SSL */

    /* close/free socket & prot layer */
    cyrus_close_sock(s->sock);
    s->sock = -1;
    
    prot_free(s->in);
    prot_free(s->out);
    s->in = s->out = NULL;

    /* Free saslconn */
    if(s->saslconn) {
	sasl_dispose(&(s->saslconn));
	s->saslconn = NULL;
    }

    /* free last_result buffer */
    buf_free(&s->last_result);

    forget_capabilities(s);
}
Example #4
0
/* we need this separate from the free() call so that we can reuse
 * the same memory for referrals */
static void sieve_dispose(isieve_t *obj) 
{
    if(!obj) return;
    sasl_dispose(&obj->conn);
    free(obj->serverFQDN);

    free(obj->refer_authinfo);
    free(obj->refer_callbacks);

    prot_free(obj->pin);
    prot_free(obj->pout);
}
Example #5
0
static void sync_reset(void)
{
    proc_cleanup();

    if (sync_in) {
        prot_NONBLOCK(sync_in);
        prot_fill(sync_in);

        prot_free(sync_in);
    }

    if (sync_out) {
        prot_flush(sync_out);
        prot_free(sync_out);
    }

    sync_in = sync_out = NULL;

#ifdef HAVE_SSL
    if (tls_conn) {
        tls_reset_servertls(&tls_conn);
        tls_conn = NULL;
    }
#endif

    cyrus_reset_stdio();

    sync_clienthost = "[local]";
    if (sync_logfd != -1) {
        close(sync_logfd);
        sync_logfd = -1;
    }
    if (sync_userid != NULL) {
        free(sync_userid);
        sync_userid = NULL;
    }
    if (sync_authstate) {
        auth_freestate(sync_authstate);
        sync_authstate = NULL;
    }
    if (sync_saslconn) {
        sasl_dispose(&sync_saslconn);
        sync_saslconn = NULL;
    }
    sync_starttls_done = 0;
    sync_compress_done = 0;

    saslprops_reset(&saslprops);
}
Example #6
0
/*
 * Finish reading a sync log file.  Closes the file (and, if the reader
 * is reading from a channel, unlinks the work file and prepares for the
 * next file).  Returns 0 on success or an IMAP error code on failure.
 */
EXPORTED int sync_log_reader_end(sync_log_reader_t *slr)
{
    if (!slr->input)
	return 0;

    if (slr->input) {
	prot_free(slr->input);
	slr->input = NULL;
    }

    if (slr->fd_is_ours && slr->fd >= 0) {
	lock_unlock(slr->fd, slr->work_file);
	close(slr->fd);
	slr->fd = -1;
    }

    if (slr->log_file) {
	/* We were initialised with a sync log channel, whose
	 * log file we rename()d to the work file.  Now that
	 * we've done with the work file we can unlink it.
	 * Further checks at this point are just paranoia. */
	if (slr->work_file && unlink(slr->work_file) < 0) {
	    syslog(LOG_ERR, "Unlink %s failed: %m", slr->work_file);
	    return IMAP_IOERROR;
	}
    }

    return 0;
}
Example #7
0
EXPORTED void dlist_printbuf(const struct dlist *dl, int printkeys, struct buf *outbuf)
{
    struct protstream *outstream;

    outstream = prot_writebuf(outbuf);
    dlist_print(dl, printkeys, outstream);
    prot_flush(outstream);
    prot_free(outstream);
}
Example #8
0
/*
 * Free a sync log reader object.
 */
EXPORTED void sync_log_reader_free(sync_log_reader_t *slr)
{
    if (!slr) return;
    if (slr->input) prot_free(slr->input);
    if (slr->fd_is_ours && slr->fd >= 0) close(slr->fd);
    free(slr->log_file);
    free(slr->work_file);
    buf_free(&slr->type);
    buf_free(&slr->arg1);
    buf_free(&slr->arg2);
    free(slr);
}
Example #9
0
EXPORTED int command_done_stdin(struct command *cmd)
{
    int r = 0;

    if (cmd->stdin_prot) {
        r = prot_flush(cmd->stdin_prot);
        close(cmd->stdin_prot->fd);
        prot_free(cmd->stdin_prot);
        cmd->stdin_prot = NULL;
    }
    return r;
}
Example #10
0
int wrap_getsint32(const char *s, int32_t *valp)
{
    struct protstream *prot;
    char *b;
    int c;

    b = xstrdup(s);	/* work around bug in prot_ungetc */
    prot = prot_readmap(b, strlen(b));
    *valp = CANARY;
    c = getsint32(prot, valp);
    free(b);
    prot_free(prot);

    return c;
}
Example #11
0
EXPORTED int dlist_parsemap(struct dlist **dlp, int parsekey,
                   const char *base, unsigned len)
{
    struct protstream *stream;
    int c;
    struct dlist *dl = NULL;

    stream = prot_readmap(base, len);
    prot_setisclient(stream, 1); /* don't sync literals */
    c = dlist_parse(&dl, parsekey, stream, NULL);
    prot_free(stream);

    if (c != EOF) {
        dlist_free(&dl);
        return IMAP_IOERROR; /* failed to slurp entire buffer */
    }

    *dlp = dl;

    return 0;
}
Example #12
0
void msg_free(message_data_t *m)
{
    int i;

    if (m->data) {
	prot_free(m->data);
    }
    if (m->f) {
	fclose(m->f);
    }
    if (m->id) {
	free(m->id);
    }

    if (m->return_path) {
	free(m->return_path);
    }
    if (m->rcpt) {
	for (i = 0; i < m->rcpt_num; i++) {
	    if (m->rcpt[i]->all) free(m->rcpt[i]->all);
	    if (m->rcpt[i]->rcpt) free(m->rcpt[i]->rcpt);
	    free(m->rcpt[i]);
	}
	free(m->rcpt);
    }
    if (m->date) {
      free(m->date);
     }

    if (m->authuser) {
	free(m->authuser);
	if (m->authstate) auth_freestate(m->authstate);
    }

    spool_free_hdrcache(m->hdrcache);

    free(m);
}
Example #13
0
EXPORTED struct backend *backend_connect(struct backend *ret_backend, const char *server,
				struct protocol_t *prot, const char *userid,
				sasl_callback_t *cb, const char **auth_status,
				int logfd)

{
    /* need to (re)establish connection to server or create one */
    int sock = -1;
    int r;
    int err = -1;
    int do_tls = 0;
    int noauth = 0;
    struct addrinfo hints, *res0 = NULL, *res;
    struct sockaddr_un sunsock;
    struct backend *ret;

    if (!ret_backend) {
	ret = xzmalloc(sizeof(struct backend));
	strlcpy(ret->hostname, server, sizeof(ret->hostname));
	ret->timeout = NULL;
    }
    else
	ret = ret_backend;

    if (server[0] == '/') { /* unix socket */
	res0 = &hints;
	memset(res0, 0, sizeof(struct addrinfo));
	res0->ai_family = PF_UNIX;
	res0->ai_socktype = SOCK_STREAM;

	res0->ai_addr = (struct sockaddr *) &sunsock;
	res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(server) + 1;
#ifdef SIN6_LEN
	res0->ai_addrlen += sizeof(sunsock.sun_len);
	sunsock.sun_len = res0->ai_addrlen;
#endif
	sunsock.sun_family = AF_UNIX;
	strlcpy(sunsock.sun_path, server, sizeof(sunsock.sun_path));

	if (!strcmp(prot->sasl_service, "lmtp") ||
	    !strcmp(prot->sasl_service, "csync")) {
	    noauth = 1;
	}
    }
    else { /* inet socket */
	char host[1024], *p;
	const char *service = prot->service;

	/* Parse server string for possible port and options */
	strlcpy(host, server, sizeof(host));
	if ((p = strchr(host, ':'))) {
	    *p++ = '\0';
	    service = p;

	    if ((p = strchr(service, '/'))) {
		tok_t tok;
		char *opt;

		*p++ = '\0';
		tok_initm(&tok, p, "/", 0);
		while ((opt = tok_next(&tok))) {
		    if (!strcmp(opt, "tls")) do_tls = 1;
		    else if (!strcmp(opt, "noauth")) noauth = 1;
		}
		tok_fini(&tok);
	    }
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	err = getaddrinfo(host, service, &hints, &res0);
	if (err) {
	    syslog(LOG_ERR, "getaddrinfo(%s) failed: %s",
		   server, gai_strerror(err));
	    goto error;
	}
    }

    for (res = res0; res; res = res->ai_next) {
	sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if (sock < 0)
	    continue;

	/* Do a non-blocking connect() */
	nonblock(sock, 1);
	if (!connect(sock, res->ai_addr, res->ai_addrlen)) {
	    /* connect() succeeded immediately */
	    break;
	}
	else if (errno == EINPROGRESS) {
	    /* connect() in progress */
	    int n;
	    fd_set wfds, rfds;
	    time_t now = time(NULL);
	    time_t timeout = now + config_getint(IMAPOPT_CLIENT_TIMEOUT);
	    struct timeval waitfor;

	    /* select() socket for writing until we succeed, fail, or timeout */
	    do {
		FD_ZERO(&wfds);
		FD_SET(sock, &wfds);
		rfds = wfds;
		waitfor.tv_sec = timeout - now;
		waitfor.tv_usec = 0;

		n = select(sock + 1, &rfds, &wfds, NULL, &waitfor);
		now = time(NULL);

		/* Retry select() if interrupted */
	    } while (n < 0 && errno == EINTR && now < timeout);

	    if (!n) {
		/* select() timed out */
		errno = ETIMEDOUT;
	    }
	    else if (FD_ISSET(sock, &rfds) || FD_ISSET(sock, &wfds)) {
		/* Socket is ready for I/O - get SO_ERROR to determine status */
		socklen_t errlen = sizeof(err);

		if (!getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &errlen) &&
		    !(errno = err)) {
		    /* connect() succeeded */
		    break;
		}
	    }
	}

	close(sock);
	sock = -1;
    }

    if (sock < 0) {
	if (res0 != &hints) freeaddrinfo(res0);
	syslog(LOG_ERR, "connect(%s) failed: %m", server);
	goto error;
    }

    /* Reset socket to blocking */
    nonblock(sock, 0);

    memcpy(&ret->addr, res->ai_addr, res->ai_addrlen);
    if (res0 != &hints)
	freeaddrinfo(res0);

    ret->in = prot_new(sock, 0);
    ret->out = prot_new(sock, 1);
    ret->sock = sock;
    prot_settimeout(ret->in, config_getint(IMAPOPT_CLIENT_TIMEOUT));
    prot_setflushonread(ret->in, ret->out);
    ret->prot = prot;

    /* use literal+ to send literals */
    prot_setisclient(ret->in, 1);
    prot_setisclient(ret->out, 1);

    /* Start TLS if required */
    if (do_tls) r = backend_starttls(ret, NULL, NULL, NULL);

    /* Login to the server */
    if (prot->type == TYPE_SPEC)
	r = prot->u.spec.login(ret, userid, cb, auth_status, noauth);
    else
	r = backend_login(ret, userid, cb, auth_status, noauth);

    if (r) goto error;

    if (logfd >= 0) {
	prot_setlog(ret->in, logfd);
	prot_setlog(ret->out, logfd);
    }
    else prot_settimeout(ret->in, 0);

    return ret;

error:
    forget_capabilities(ret);
    if (ret->in) {
	prot_free(ret->in);
	ret->in = NULL;
    }
    if (ret->out) {
	prot_free(ret->out);
	ret->out = NULL;
    }
    if (sock >= 0)
	close(sock);
    if (ret->saslconn) {
	sasl_dispose(&ret->saslconn);
	ret->saslconn = NULL;
    }
    if (!ret_backend)
	free(ret);
    return NULL;
}
Example #14
0
static int _verify_message_cb(const struct backup_message *message, void *rock)
{
    struct verify_message_rock *vmrock = (struct verify_message_rock *) rock;
    struct dlist *dl = NULL;
    struct dlist *di = NULL;
    FILE *out = vmrock->out;
    int r;

    /* cache the dlist so that multiple reads from the same offset don't
     * cause expensive reverse seeks in decompression stream
     */
    if (!vmrock->cached_dlist || vmrock->cached_offset != message->offset) {
        if (vmrock->cached_dlist) {
            dlist_unlink_files(vmrock->cached_dlist);
            dlist_free(&vmrock->cached_dlist);
        }

        r = gzuc_seekto(vmrock->gzuc, message->offset);
        if (r) return r;

        struct protstream *ps = prot_readcb(_prot_fill_cb, vmrock->gzuc);
        prot_setisclient(ps, 1); /* don't sync literals */
        r = parse_backup_line(ps, NULL, NULL, &dl);
        prot_free(ps);

        if (r == EOF) {
            const char *error = prot_error(ps);
            if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
                syslog(LOG_ERR,
                       "%s: error reading message %i at offset %jd, byte %i: %s",
                       __func__, message->id, message->offset, prot_bytes_in(ps), error);
                if (out)
                    fprintf(out, "error reading message %i at offset %jd, byte %i: %s",
                            message->id, message->offset, prot_bytes_in(ps), error);
            }
            return r;
        }

        vmrock->cached_dlist = dl;
        vmrock->cached_offset = message->offset;
    }
    else {
        dl = vmrock->cached_dlist;
    }

    r = strcmp(dl->name, "MESSAGE");
    if (r) return r;

    r = -1;
    for (di = dl->head; di; di = di->next) {
        struct message_guid *guid = NULL;
        const char *fname = NULL;

        if (!dlist_tofile(di, NULL, &guid, NULL, &fname))
            continue;

        r = message_guid_cmp(guid, message->guid);
        if (!r) {
            if (vmrock->verify_guid) {
                const char *msg_base = NULL;
                size_t msg_len = 0;
                struct message_guid computed_guid;
                int fd;

                fd = open(fname, O_RDWR);
                if (fd != -1) {
                    map_refresh(fd, 1, &msg_base, &msg_len, MAP_UNKNOWN_LEN, fname, NULL);

                    message_guid_generate(&computed_guid, msg_base, msg_len);
                    r = message_guid_cmp(&computed_guid, message->guid);
                    if (r && out)
                        fprintf(out, "guid mismatch for message %i\n", message->id);

                    map_free(&msg_base, &msg_len);
                    close(fd);
                }
                else {
                    syslog(LOG_ERR, "IOERROR: %s open %s: %m", __func__, fname);
                    if (out)
                        fprintf(out, "error reading staging file for message %i\n", message->id);
                    r = -1;
                }
            }
            break;
        }
    }

    return r;
}
Example #15
0
/* verify that the matching MAILBOX exists within the claimed chunk
 * for each mailbox or mailbox_message in the index
 */
static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk *chunk,
                                      struct gzuncat *gzuc, int verbose, FILE *out)
{
    /*
     *   get list of mailboxes in chunk
     *   get list of mailbox_messages in chunk
     *   index mailboxes list by uniqueid
     *   index mailbox_messages list by uniqueid:uid
     *   open chunk
     *   foreach line in chunk
     *     read dlist
     *     skip if it's not a mailbox
     *     if details in dlist match details in mailbox
     *       remove from mailbox list/index
     *     foreach record in dlist
     *       if details in dlist match details in mailbox_message
     *       remove from mailbox_message list/index
     *   failed if either list of mailboxes or list of mailbox_messages is not empty
     */

    struct backup_mailbox_list *mailbox_list = NULL;
    struct backup_mailbox_message_list *mailbox_message_list = NULL;
    hash_table mailbox_list_index = HASH_TABLE_INITIALIZER;
    hash_table mailbox_message_list_index = HASH_TABLE_INITIALIZER;
    struct backup_mailbox *mailbox = NULL;
    struct backup_mailbox_message *mailbox_message = NULL;
    int r;

    if (out && verbose)
        fprintf(out, "checking chunk %d mailbox links...\n", chunk->id);

    mailbox_list = backup_get_mailboxes(backup, chunk->id, 0);
    mailbox_message_list = backup_get_mailbox_messages(backup, chunk->id);

    if (mailbox_list->count == 0 && mailbox_message_list->count == 0) {
        /* nothing we care about in this chunk */
        free(mailbox_list);
        free(mailbox_message_list);
        if (out && verbose)
            fprintf(out, "ok\n");
        return 0;
    }

    /* XXX consider whether the two hashes should use pools */

    if (mailbox_list->count) {
        /* build an index of the mailbox list */
        construct_hash_table(&mailbox_list_index, mailbox_list->count, 0);
        mailbox = mailbox_list->head;
        while (mailbox) {
            hash_insert(mailbox->uniqueid, mailbox, &mailbox_list_index);
            mailbox = mailbox->next;
        }
    }

    if (mailbox_message_list->count) {
        /* build an index of the mailbox message list */
        construct_hash_table(&mailbox_message_list_index,
                             mailbox_message_list->count, 0);
        mailbox_message = mailbox_message_list->head;
        while (mailbox_message) {
            char keybuf[1024]; // FIXME whatever
            snprintf(keybuf, sizeof(keybuf), "%s:%d",
                     mailbox_message->mailbox_uniqueid, mailbox_message->uid);
            hash_insert(keybuf, mailbox_message, &mailbox_message_list_index);
            mailbox_message = mailbox_message->next;
        }
    }

    r = gzuc_member_start_from(gzuc, chunk->offset);
    if (r) {
        syslog(LOG_ERR, "%s: error reading chunk %i at offset %jd: %s",
                        __func__, chunk->id, chunk->offset, zError(r));
        if (out)
            fprintf(out, "error reading chunk %i at offset %jd: %s",
                    chunk->id, chunk->offset, zError(r));
        goto done;
    }
    struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc);
    prot_setisclient(ps, 1); /* don't sync literals */

    struct buf cmd = BUF_INITIALIZER;
    while (1) {
        struct dlist *dl = NULL;
        struct dlist *record = NULL;
        struct dlist *di = NULL;
        const char *uniqueid = NULL;

        int c = parse_backup_line(ps, NULL, &cmd, &dl);
        if (c == EOF) {
            const char *error = prot_error(ps);
            if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
                syslog(LOG_ERR,
                       "%s: error reading chunk %i data at offset %jd, byte %i: %s",
                       __func__, chunk->id, chunk->offset, prot_bytes_in(ps), error);
                if (out)
                    fprintf(out, "error reading chunk %i data at offset %jd, byte %i: %s",
                            chunk->id, chunk->offset, prot_bytes_in(ps), error);
                r = EOF;
            }
            break;
        }

        if (strcmp(buf_cstring(&cmd), "APPLY") != 0)
            goto next_line;

        if (strcmp(dl->name, "MAILBOX") != 0)
            goto next_line;

        if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
            goto next_line;

        if (mailbox_list->count) {
            mailbox = (struct backup_mailbox *) hash_lookup(uniqueid, &mailbox_list_index);

            if (mailbox && mailbox_matches(mailbox, dl)) {
                backup_mailbox_list_remove(mailbox_list, mailbox);
                hash_del(uniqueid, &mailbox_list_index);
                backup_mailbox_free(&mailbox);
            }
        }

        if (mailbox_message_list->count) {
            if (!dlist_getlist(dl, "RECORD", &record))
                goto next_line;

            for (di = record->head; di; di = di->next) {
                char keybuf[1024]; // FIXME whatever
                uint32_t uid;

                if (!dlist_getnum32(di, "UID", &uid))
                    continue;

                snprintf(keybuf, sizeof(keybuf), "%s:%d", uniqueid, uid);
                mailbox_message = (struct backup_mailbox_message *) hash_lookup(
                    keybuf, &mailbox_message_list_index);

                if (!mailbox_message)
                    continue;

                if (!mailbox_message_matches(mailbox_message, di))
                    continue;

                backup_mailbox_message_list_remove(mailbox_message_list, mailbox_message);
                hash_del(keybuf, &mailbox_message_list_index);
                backup_mailbox_message_free(&mailbox_message);
            }
        }

next_line:
        if (dl) {
            dlist_unlink_files(dl);
            dlist_free(&dl);
        }
    }
    buf_free(&cmd);

    prot_free(ps);
    gzuc_member_end(gzuc, NULL);

    /* anything left in either of the lists is missing from the chunk data. bad! */
    mailbox = mailbox_list->head;
    while (mailbox) {
        syslog(LOG_DEBUG, "%s: chunk %d missing mailbox data for %s (%s)\n",
                __func__, chunk->id, mailbox->uniqueid, mailbox->mboxname);
        if (out)
            fprintf(out, "chunk %d missing mailbox data for %s (%s)\n",
                    chunk->id, mailbox->uniqueid, mailbox->mboxname);
        mailbox = mailbox->next;
    }

    mailbox_message = mailbox_message_list->head;
    while (mailbox_message) {
        syslog(LOG_DEBUG, "%s: chunk %d missing mailbox_message data for %s uid %u\n",
                __func__, chunk->id, mailbox_message->mailbox_uniqueid,
                mailbox_message->uid);
        if (out)
            fprintf(out, "chunk %d missing mailbox_message data for %s uid %u\n",
                    chunk->id, mailbox_message->mailbox_uniqueid,
                    mailbox_message->uid);
        mailbox_message = mailbox_message->next;
    }

    if (!r) r = mailbox_list->count || mailbox_message_list->count ? -1 : 0;

done:
    free_hash_table(&mailbox_list_index, NULL);
    free_hash_table(&mailbox_message_list_index, NULL);

    backup_mailbox_list_empty(mailbox_list);
    free(mailbox_list);

    backup_mailbox_message_list_empty(mailbox_message_list);
    free(mailbox_message_list);

    syslog(LOG_DEBUG, "%s: chunk %d %s!\n", __func__, chunk->id,
            r ? "failed" : "passed");
    if (out && verbose)
        fprintf(out, "%s\n", r ? "error" : "ok");
    return r;
}
Example #16
0
struct backend *backend_connect(struct backend *ret_backend, const char *server,
				struct protocol_t *prot, const char *userid,
				sasl_callback_t *cb, const char **auth_status)
{
    /* need to (re)establish connection to server or create one */
    int sock = -1;
    int r;
    int err = -1;
    int ask = 1; /* should we explicitly ask for capabilities? */
    struct addrinfo hints, *res0 = NULL, *res;
    struct sockaddr_un sunsock;
    char buf[2048];
    struct sigaction action;
    struct backend *ret;
    char rsessionid[MAX_SESSIONID_SIZE];

    if (!ret_backend) {
	ret = xzmalloc(sizeof(struct backend));
	strlcpy(ret->hostname, server, sizeof(ret->hostname));
	ret->timeout = NULL;
    }
    else
	ret = ret_backend;

    if (server[0] == '/') { /* unix socket */
	res0 = &hints;
	memset(res0, 0, sizeof(struct addrinfo));
	res0->ai_family = PF_UNIX;
	res0->ai_socktype = SOCK_STREAM;

 	res0->ai_addr = (struct sockaddr *) &sunsock;
 	res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(server) + 1;
#ifdef SIN6_LEN
 	res0->ai_addrlen += sizeof(sunsock.sun_len);
 	sunsock.sun_len = res0->ai_addrlen;
#endif
	sunsock.sun_family = AF_UNIX;
	strlcpy(sunsock.sun_path, server, sizeof(sunsock.sun_path));

	/* XXX set that we are preauthed */

	/* change hostname to 'config_servername' */
	strlcpy(ret->hostname, config_servername, sizeof(ret->hostname));
    }
    else { /* inet socket */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	err = getaddrinfo(server, prot->service, &hints, &res0);
	if (err) {
	    syslog(LOG_ERR, "getaddrinfo(%s) failed: %s",
		   server, gai_strerror(err));
	    goto error;
	}
    }

    /* Setup timeout */
    timedout = 0;
    action.sa_flags = 0;
    action.sa_handler = timed_out;
    sigemptyset(&action.sa_mask);
    if(sigaction(SIGALRM, &action, NULL) < 0) 
    {
	syslog(LOG_ERR, "Setting timeout in backend_connect failed: sigaction: %m");
	/* continue anyway */
    }
    
    for (res = res0; res; res = res->ai_next) {
	sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if (sock < 0)
	    continue;
	alarm(config_getint(IMAPOPT_CLIENT_TIMEOUT));
	if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
	    break;
	if(errno == EINTR && timedout == 1)
	    errno = ETIMEDOUT;
	close(sock);
	sock = -1;
    }

    /* Remove timeout code */
    alarm(0);
    signal(SIGALRM, SIG_IGN);
    
    if (sock < 0) {
	if (res0 != &hints)
	    freeaddrinfo(res0);
	syslog(LOG_ERR, "connect(%s) failed: %m", server);
	goto error;
    }
    memcpy(&ret->addr, res->ai_addr, res->ai_addrlen);
    if (res0 != &hints)
	freeaddrinfo(res0);

    ret->in = prot_new(sock, 0);
    ret->out = prot_new(sock, 1);
    ret->sock = sock;
    prot_setflushonread(ret->in, ret->out);
    ret->prot = prot;

    /* use literal+ to send literals */
    prot_setisclient(ret->in, 1);
    prot_setisclient(ret->out, 1);
    
    if (prot->banner.auto_capa) {
	/* try to get the capabilities from the banner */
	r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER);
	if (r) {
	    /* found capabilities in banner -> don't ask */
	    ask = 0;
	}
    }
    else {
	do { /* read the initial greeting */
	    if (!prot_fgets(buf, sizeof(buf), ret->in)) {
		syslog(LOG_ERR,
		       "backend_connect(): couldn't read initial greeting: %s",
		       ret->in->error ? ret->in->error : "(null)");
		goto error;
	    }
	} while (strncasecmp(buf, prot->banner.resp,
			     strlen(prot->banner.resp)));
	strncpy(ret->banner, buf, 2048);
    }

    if (ask) {
	/* get the capabilities */
	ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO);
    }

    /* now need to authenticate to backend server,
       unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */
    if ((server[0] != '/') ||
	(strcmp(prot->sasl_service, "lmtp") &&
	 strcmp(prot->sasl_service, "csync"))) {
	char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
	const char *my_status;

	if ((r = backend_authenticate(ret, userid, cb, &my_status))) {
	    syslog(LOG_ERR, "couldn't authenticate to backend server: %s",
		   sasl_errstring(r, NULL, NULL));
	    free(old_mechlist);
	    goto error;
	}
	else {
	    const void *ssf;

	    sasl_getprop(ret->saslconn, SASL_SSF, &ssf);
	    if (*((sasl_ssf_t *) ssf)) {
		/* if we have a SASL security layer, compare SASL mech lists
		   before/after AUTH to check for a MITM attack */
		char *new_mechlist;
		int auto_capa = (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF);

		if (!strcmp(prot->service, "sieve")) {
		    /* XXX  Hack to handle ManageSieve servers.
		     * No way to tell from protocol if server will
		     * automatically send capabilities, so we treat it
		     * as optional.
		     */
		    char ch;

		    /* wait and probe for possible auto-capability response */
		    usleep(250000);
		    prot_NONBLOCK(ret->in);
		    if ((ch = prot_getc(ret->in)) != EOF) {
			prot_ungetc(ch, ret->in);
		    } else {
			auto_capa = AUTO_CAPA_AUTH_NO;
		    }
		    prot_BLOCK(ret->in);
		}

		ask_capability(ret, /*dobanner*/0, auto_capa);
		new_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
		if (new_mechlist &&
		    old_mechlist &&
		    strcmp(new_mechlist, old_mechlist)) {
		    syslog(LOG_ERR, "possible MITM attack:"
			   "list of available SASL mechanisms changed");
		    free(new_mechlist);
		    free(old_mechlist);
		    goto error;
		}
		free(new_mechlist);
	    }
	    else if (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) {
		/* try to get the capabilities from the AUTH success response */
		forget_capabilities(ret);
		parse_capability(ret, my_status);
		post_parse_capability(ret);
	    }

	    if (!(strcmp(prot->service, "imap") &&
		 (strcmp(prot->service, "pop3")))) {
		parse_sessionid(my_status, rsessionid);
		syslog(LOG_NOTICE, "proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid);
	    }
	}

	if (auth_status) *auth_status = my_status;
	free(old_mechlist);
    }

    /* start compression if requested and both client/server support it */
    if (config_getswitch(IMAPOPT_PROXY_COMPRESS) && ret &&
	CAPA(ret, CAPA_COMPRESS) &&
	prot->compress_cmd.cmd &&
	do_compress(ret, &prot->compress_cmd)) {

	syslog(LOG_ERR, "couldn't enable compression on backend server");
	goto error;
    }

    return ret;

error:
    forget_capabilities(ret);
    if (ret->in) {
	prot_free(ret->in);
	ret->in = NULL;
    }
    if (ret->out) {
	prot_free(ret->out);
	ret->out = NULL;
    }
    if (sock >= 0)
	close(sock);
    if (ret->saslconn) {
	sasl_dispose(&ret->saslconn);
	ret->saslconn = NULL;
    }
    if (!ret_backend)
	free(ret);
    return NULL;
}
Example #17
0
HIDDEN void ws_input(struct transaction_t *txn)
{
    struct ws_context *ctx = (struct ws_context *) txn->ws_ctx;
    wslay_event_context_ptr ev = ctx->event;
    int want_read = wslay_event_want_read(ev);
    int want_write = wslay_event_want_write(ev);
    int goaway = txn->flags.conn & CONN_CLOSE;

    syslog(LOG_DEBUG, "ws_input()  eof: %d, want read: %d, want write: %d",
           txn->conn->pin->eof, want_read, want_write);

    if (want_read && !goaway) {
        /* Read frame(s) */
        if (txn->conn->sess_ctx) {
            /* Data has been read into request body */
            ctx->h2_pin = prot_readmap(buf_base(&txn->req_body.payload),
                                       buf_len(&txn->req_body.payload));
        }

        int r = wslay_event_recv(ev);

        if (txn->conn->sess_ctx) {
            buf_reset(&txn->req_body.payload);
            prot_free(ctx->h2_pin);
        }

        if (!r) {
            /* Successfully received frames */
            syslog(LOG_DEBUG, "ws_event_recv: success");
        }
        else if (r == WSLAY_ERR_NO_MORE_MSG) {
            /* Client closed connection */
            syslog(LOG_DEBUG, "client closed connection");
            txn->flags.conn = CONN_CLOSE;
        }
        else {
            /* Failure */
            syslog(LOG_DEBUG, "ws_event_recv: %s (%s)",
                   wslay_strerror(r), prot_error(txn->conn->pin));
            goaway = 1;

            if (r == WSLAY_ERR_CALLBACK_FAILURE) {
                /* Client timeout */
                txn->error.desc = prot_error(txn->conn->pin);
            }
            else {
                txn->error.desc = wslay_strerror(r);
            }
        }
    }
    else if (!want_write) {
        /* Connection is done */
        syslog(LOG_DEBUG, "connection closed");
        txn->flags.conn = CONN_CLOSE;
    }

    if (goaway) {
        /* Tell client we are closing session */
        syslog(LOG_WARNING, "%s, closing connection", txn->error.desc);

        syslog(LOG_DEBUG, "wslay_event_queue_close()");
        int r = wslay_event_queue_close(ev, WSLAY_CODE_GOING_AWAY,
                                        (uint8_t *) txn->error.desc,
                                        strlen(txn->error.desc));
        if (r) {
            syslog(LOG_ERR,
                   "wslay_event_queue_close: %s", wslay_strerror(r));
        }

        txn->flags.conn = CONN_CLOSE;
    }

    /* Write frame(s) */
    ws_output(txn);

    return;
}
Example #18
0
int main(int argc, char *argv[])
{
    extern char *optarg;
    int opt;
    char *alt_config = NULL, *port = "119";
    const char *peer = NULL, *server = "localhost", *wildmat = "*";
    char *authname = NULL, *password = NULL;
    int psock = -1, ssock = -1;
    struct protstream *pin, *pout, *sin, *sout;
    char buf[BUFFERSIZE];
    char sfile[1024] = "";
    int fd = -1, i, offered, rejected, accepted, failed;
    time_t stamp;
    strarray_t resp = STRARRAY_INITIALIZER;
    int newnews = 1;
    char *datefmt = "%y%m%d %H%M%S";

    if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) {
	fatal("must run as the Cyrus user", EC_USAGE);
    }

    while ((opt = getopt(argc, argv, "C:s:w:f:a:p:ny")) != EOF) {
	switch (opt) {
	case 'C': /* alt config file */
	    alt_config = optarg;
	    break;

	case 's': /* server */
	    server = xstrdup(optarg);
	    if ((port = strchr(server, ':')))
		*port++ = '\0';
	    else
		port = "119";
	    break;

	case 'w': /* wildmat */
	    wildmat = optarg;
	    break;

	case 'f': /* timestamp file */
	    snprintf(sfile, sizeof(sfile), "%s", optarg);
	    break;

	case 'a': /* authname */
	    authname = optarg;
	    break;

	case 'p': /* password */
	    password = optarg;
	    break;

	case 'n': /* no newnews */
	    newnews = 0;
	    break;

	case 'y': /* newsserver is y2k compliant */
	    datefmt = "%Y%m%d %H%M%S";
	    break;

	default:
	    usage();
	    /* NOTREACHED */
	}
    }
    if (argc - optind < 1) {
	usage();
	/* NOTREACHED */
    }

    peer = argv[optind++];

    cyrus_init(alt_config, "fetchnews", 0, 0);

    /* connect to the peer */
    /* xxx configurable port number? */
    if ((psock = init_net(peer, "119", &pin, &pout)) < 0) {
	fprintf(stderr, "connection to %s failed\n", peer);
	cyrus_done();
	exit(-1);
    }

    /* read the initial greeting */
    if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("20", buf, 2)) {
	syslog(LOG_ERR, "peer not available");
	goto quit;
    }

    if (authname) {
	/* authenticate to peer */
	/* XXX this should be modified to support SASL and STARTTLS */

	prot_printf(pout, "AUTHINFO USER %s\r\n", authname);
	if (!prot_fgets(buf, sizeof(buf), pin)) {
	    syslog(LOG_ERR, "AUTHINFO USER terminated abnormally");
	    goto quit;
	}
	else if (!strncmp("381", buf, 3)) {
	    /* password required */
	    if (!password)
		password = cyrus_getpass("Please enter the password: "******"failed to get password\n");
		goto quit;
	    }

	    prot_printf(pout, "AUTHINFO PASS %s\r\n", password);
	    if (!prot_fgets(buf, sizeof(buf), pin)) {
		syslog(LOG_ERR, "AUTHINFO PASS terminated abnormally");
		goto quit;
	    }
	}

	if (strncmp("281", buf, 3)) {
	    /* auth failed */
	    goto quit;
	}
    }

    /* change to reader mode - not always necessary, so ignore result */
    prot_printf(pout, "MODE READER\r\n");
    prot_fgets(buf, sizeof(buf), pin);

    if (newnews) {
	struct tm ctime, *ptime;

	/* fetch the server's current time */
	prot_printf(pout, "DATE\r\n");

	if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("111 ", buf, 4)) {
	    syslog(LOG_ERR, "error fetching DATE");
	    goto quit;
	}

	/* parse and normalize the server time */
	memset(&ctime, 0, sizeof(struct tm));
	sscanf(buf+4, "%4d%02d%02d%02d%02d%02d",
	       &ctime.tm_year, &ctime.tm_mon, &ctime.tm_mday,
	       &ctime.tm_hour, &ctime.tm_min, &ctime.tm_sec);
	ctime.tm_year -= 1900;
	ctime.tm_mon--;
	ctime.tm_isdst = -1;

	/* read the previous timestamp */
	if (!sfile[0]) {
	    char oldfile[1024];

	    snprintf(sfile, sizeof(sfile), "%s/fetchnews.stamp", config_dir);

	    /* upgrade from the old stamp filename to the new */
	    snprintf(oldfile, sizeof(oldfile), "%s/newsstamp", config_dir);
	    rename(oldfile, sfile);
	}

	if ((fd = open(sfile, O_RDWR | O_CREAT, 0644)) == -1) {
	    syslog(LOG_ERR, "cannot open %s", sfile);
	    goto quit;
	}
	if (lock_nonblocking(fd) == -1) {
	    syslog(LOG_ERR, "cannot lock %s: %m", sfile);
	    goto quit;
	}

	if (read(fd, &stamp, sizeof(stamp)) < (int) sizeof(stamp)) {
	    /* XXX do something better here */
	    stamp = 0;
	}

	/* ask for new articles */
	if (stamp) stamp -= 180; /* adjust back 3 minutes */
	ptime = gmtime(&stamp);
	ptime->tm_isdst = -1;
	strftime(buf, sizeof(buf), datefmt, ptime);
	prot_printf(pout, "NEWNEWS %s %s GMT\r\n", wildmat, buf);
	
	if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("230", buf, 3)) {
	    syslog(LOG_ERR, "peer doesn't support NEWNEWS");
	    newnews = 0;
	}

	/* prepare server's current time as new timestamp */
	stamp = mktime(&ctime);
	/* adjust for local timezone

	   XXX  We need to do this because we use gmtime() above.
	   We can't change this, otherwise we'd be incompatible
	   with an old localtime timestamp.
	*/
	stamp += gmtoff_of(&ctime, stamp);
    }

    if (!newnews) {
	prot_printf(pout, "LIST ACTIVE %s\r\n", wildmat);
	
	if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("215", buf, 3)) {
	    syslog(LOG_ERR, "peer doesn't support LIST ACTIVE");
	    goto quit;
	}
    }

    /* process the NEWNEWS/LIST ACTIVE list */
    while (prot_fgets(buf, sizeof(buf), pin)) {
	if (buf[0] == '.') break;
	strarray_append(&resp, buf);
    }
    if (buf[0] != '.') {
	syslog(LOG_ERR, "%s terminated abnormally",
	       newnews ? "NEWNEWS" : "LIST ACTIVE");
	goto quit;
    }

    if (!resp.count) {
	/* nothing matches our wildmat */
	goto quit;
    }

    /* connect to the server */
    if ((ssock = init_net(server, port, &sin, &sout)) < 0) {
	fprintf(stderr, "connection to %s failed\n", server);
	goto quit;
    }

    /* read the initial greeting */
    if (!prot_fgets(buf, sizeof(buf), sin) || strncmp("20", buf, 2)) {
	syslog(LOG_ERR, "server not available");
	goto quit;
    }

    /* fetch and store articles */
    offered = rejected = accepted = failed = 0;
    if (newnews) {
	/* response is a list of msgids */
	for (i = 0; i < resp.count; i++) {
	    /* find the end of the msgid */
	    *(strrchr(resp.data[i], '>') + 1) = '\0';

	    offered++;
	    if (fetch(resp.data[i], 1, pin, pout, sin, sout,
		      &rejected, &accepted, &failed)) {
		goto quit;
	    }
	}

	/* write the current timestamp */
	lseek(fd, 0, SEEK_SET);
	if (write(fd, &stamp, sizeof(stamp)) < (int) sizeof(stamp))
	    syslog(LOG_ERR, "error writing %s", sfile);
	lock_unlock(fd);
	close(fd);
    }
    else {
	char group[BUFFERSIZE], msgid[BUFFERSIZE], lastbuf[50];
	const char *data;
	unsigned long low, high, last, cur;
	int start;
	size_t datalen;
	struct txn *tid = NULL;

	newsrc_init(NULL, 0);

	/*
	 * response is a list of groups.
	 * select each group, and STAT each article we haven't seen yet.
	 */
	for (i = 0; i < resp.count; i++) {
	    /* parse the LIST ACTIVE response */
	    sscanf(resp.data[i], "%s %lu %lu", group, &high, &low);

	    last = 0;
	    if (!cyrusdb_fetchlock(newsrc_db, group, strlen(group),
			       &data, &datalen, &tid)) {
		last = strtoul(data, NULL, 10);
	    }
	    if (high <= last) continue;

	    /* select the group */
	    prot_printf(pout, "GROUP %s\r\n", group);
	    if (!prot_fgets(buf, sizeof(buf), pin)) {
		syslog(LOG_ERR, "GROUP terminated abnormally");
		continue;
	    }
	    else if (strncmp("211", buf, 3)) break;

	    for (start = 1, cur = low > last ? low : ++last;; cur++) {
		if (start) {
		    /* STAT the first article we haven't seen */
		    prot_printf(pout, "STAT %lu\r\n", cur);
		} else {
		    /* continue with the NEXT article */
		    prot_printf(pout, "NEXT\r\n");
		}

		if (!prot_fgets(buf, sizeof(buf), pin)) {
		    syslog(LOG_ERR, "STAT/NEXT terminated abnormally");
		    cur--;
		    break;
		}
		if (!strncmp("223", buf, 3)) {
		    /* parse the STAT/NEXT response */
		    sscanf(buf, "223 %lu %s", &cur, msgid);

		    /* find the end of the msgid */
		    *(strrchr(msgid, '>') + 1) = '\0';

		    if (fetch(msgid, 0, pin, pout, sin, sout,
			      &rejected, &accepted, &failed)) {
			cur--;
			break;
		    }
		    offered++;
		    start = 0;
		}

		/* have we reached the highwater mark? */
		if (cur >= high) break;
	    }

	    snprintf(lastbuf, sizeof(lastbuf), "%lu", cur);
	    cyrusdb_store(newsrc_db, group, strlen(group),
		      lastbuf, strlen(lastbuf)+1, &tid);
	}

	if (tid) cyrusdb_commit(newsrc_db, tid);
	newsrc_done();
    }

    syslog(LOG_NOTICE,
	   "fetchnews: %s offered %d; %s rejected %d, accepted %d, failed %d",
	   peer, offered, server, rejected, accepted, failed);

  quit:
    if (psock >= 0) {
	prot_printf(pout, "QUIT\r\n");
	prot_flush(pout);

	/* Flush the incoming buffer */
	prot_NONBLOCK(pin);
	prot_fill(pin);

	/* close/free socket & prot layer */
	close(psock);
    
	prot_free(pin);
	prot_free(pout);
    }

    if (ssock >= 0) {
	prot_printf(sout, "QUIT\r\n");
	prot_flush(sout);

	/* Flush the incoming buffer */
	prot_NONBLOCK(sin);
	prot_fill(sin);

	/* close/free socket & prot layer */
	close(psock);
    
	prot_free(sin);
	prot_free(sout);
    }

    cyrus_done();
    
    return 0;
}
Example #19
0
static void test_printstring(void)
{
    PROLOG;
    struct protstream *p;
    int len;
    struct buf b = BUF_INITIALIZER;
    int i;
    char str[2600];

    p = prot_new(_fd, 1);
    CU_ASSERT_PTR_NOT_NULL_FATAL(p);

    /* NULL string */
    BEGIN;
    prot_printstring(p, NULL);
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 3);
    CU_ASSERT_STRING_EQUAL(str, "NIL");

    /* Zero length string */
    BEGIN;
    prot_printstring(p, "");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 2);
    CU_ASSERT_STRING_EQUAL(str, "\"\"");

    /* Boring string */
    BEGIN;
    prot_printstring(p, "Hello");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 7);
    CU_ASSERT_STRING_EQUAL(str, "\"Hello\"");

    /* String with non-dangerous whitespace */
    BEGIN;
    prot_printstring(p, "Hello World\tagain");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 19);
    CU_ASSERT_STRING_EQUAL(str, "\"Hello World\tagain\"");

    /* String with dangerous whitespace */
    BEGIN;
    prot_printstring(p, "Good\rBye\nEarth");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 20);
    CU_ASSERT_STRING_EQUAL(str, "{14}\r\nGood\rBye\nEarth");

    /* String with embedded dquote */
    BEGIN;
    prot_printstring(p, "Quot\"able");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 14);
    CU_ASSERT_STRING_EQUAL(str, "{9}\r\nQuot\"able");

    /* String with embedded percent */
    BEGIN;
    prot_printstring(p, "per%ent");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 12);
    CU_ASSERT(!strcmp(str, "{7}\r\nper%ent"));

    /* String with embedded backslash */
    BEGIN;
    prot_printstring(p, "slash\\dot");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 14);
    CU_ASSERT_STRING_EQUAL(str, "{9}\r\nslash\\dot");

    /* String with embedded 8-bit chars */
    BEGIN;
    prot_printstring(p, "Hi I'm \330l\345f");
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, 17);
    CU_ASSERT_STRING_EQUAL(str, "{11}\r\nHi I'm \330l\345f");

    /* Boring but overly long string */
    for (i = 0 ; i<500 ; i++)
	buf_appendcstr(&b, "blah ");
    buf_cstring(&b);
    BEGIN;
    prot_printstring(p, b.s);
    prot_flush(p);
    END(str, len);
    CU_ASSERT_EQUAL(len, b.len+8);
    CU_ASSERT_STRING_EQUAL(str+8, b.s);
    str[8] = '\0';
    CU_ASSERT_STRING_EQUAL(str, "{2500}\r\n");

    buf_free(&b);
    prot_free(p);
    EPILOG;
}
Example #20
0
/* returns:
 *   0 on success
 *   1 if compact was not needed
 *   negative on error
 */
EXPORTED int backup_compact(const char *name,
                            enum backup_open_nonblock nonblock,
                            int force, int verbose, FILE *out)
{
    struct backup *original = NULL;
    struct backup *compact = NULL;
    struct backup_chunk_list *all_chunks = NULL;
    struct backup_chunk_list *keep_chunks = NULL;
    struct backup_chunk *chunk = NULL;
    struct sync_msgid_list *keep_message_guids = NULL;
    struct gzuncat *gzuc = NULL;
    struct protstream *in = NULL;
    time_t since, chunk_start_time, ts;
    int r;

    compact_readconfig();

    r = compact_open(name, &original, &compact, nonblock);
    if (r) return r;

    /* calculate current time after obtaining locks, in case of a wait */
    const time_t now = time(NULL);

    const int retention_days = config_getint(IMAPOPT_BACKUP_RETENTION_DAYS);
    if (retention_days > 0) {
        since = now - (retention_days * 24 * 60 * 60);
    }
    else {
        /* zero or negative retention days means "keep forever" */
        since = -1;
    }

    all_chunks = backup_get_chunks(original);
    if (!all_chunks) goto error;

    keep_chunks = backup_get_live_chunks(original, since);
    if (!keep_chunks) goto error;

    if (!force && !compact_required(all_chunks, keep_chunks)) {
        /* nothing to do */
        backup_chunk_list_free(&all_chunks);
        backup_chunk_list_free(&keep_chunks);
        backup_unlink(&compact);
        backup_close(&original);
        return 1;
    }

    if (verbose) {
        fprintf(out, "keeping " SIZE_T_FMT " chunks:\n", keep_chunks->count);

        for (chunk = keep_chunks->head; chunk; chunk = chunk->next) {
            fprintf(out, " %d", chunk->id);
        }

        fprintf(out, "\n");
    }

    gzuc = gzuc_new(original->fd);
    if (!gzuc) goto error;

    chunk_start_time = -1;
    ts = 0;
    struct buf cmd = BUF_INITIALIZER;
    for (chunk = keep_chunks->head; chunk; chunk = chunk->next) {
        keep_message_guids = sync_msgid_list_create(0);
        r = backup_message_foreach(original, chunk->id, &since,
                                   _keep_message_guids_cb, keep_message_guids);
        if (r) goto error;

        gzuc_member_start_from(gzuc, chunk->offset);

        in = prot_readcb(_prot_fill_cb, gzuc);

        while (1) {
            struct dlist *dl = NULL;

            int c = parse_backup_line(in, &ts, &cmd, &dl);

            if (c == EOF) {
                const char *error = prot_error(in);
                if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
                    syslog(LOG_ERR,
                           "IOERROR: %s: error reading chunk at offset %jd, byte %i: %s\n",
                           name, chunk->offset, prot_bytes_in(in), error);

                    if (out)
                        fprintf(out, "error reading chunk at offset %jd, byte %i: %s\n",
                                chunk->offset, prot_bytes_in(in), error);

                    r = IMAP_IOERROR;
                    goto error;
                }

                break;
            }

            if (chunk_start_time == -1) {
                r = backup_append_start(compact, &ts, BACKUP_APPEND_NOFLUSH);
                if (r) goto error;
                chunk_start_time = ts;
            }

            // XXX if this line is worth keeping
            if (want_append(original, chunk->id, dl, keep_message_guids)) {
                // FIXME if message is removed due to unneeded chunk,
                // subsequent mailbox lines for it will fail here
                // so we need to be able to tell which lines apply to messages we don't want anymore
                r = backup_append(compact, dl, &ts, BACKUP_APPEND_NOFLUSH);
                if (r) goto error;
            }

            dlist_unlink_files(dl);
            dlist_free(&dl);

            // if this line put us over compact_maxsize
            if (want_split(chunk, &compact->append_state->wrote)) {
                r = backup_append_end(compact, &ts);
                chunk_start_time = -1;

                if (verbose) {
                    fprintf(out, "splitting chunk %d\n", chunk->id);
                }
            }
        }

        // if we're due to start a new chunk
        if (compact->append_state && compact->append_state->mode) {
            if (!want_combine(compact->append_state->wrote, chunk->next)) {
                r = backup_append_end(compact, &ts);
                chunk_start_time = -1;
            }
            else if (verbose) {
                fprintf(out, "combining chunks %d and %d\n",
                             chunk->id, chunk->next->id);
            }
        }

        prot_free(in);
        in = NULL;
        gzuc_member_end(gzuc, NULL);

        sync_msgid_list_free(&keep_message_guids);
    }
    buf_free(&cmd);

    if (compact->append_state && compact->append_state->mode)
        backup_append_end(compact, &ts);

    gzuc_free(&gzuc);

    backup_chunk_list_free(&keep_chunks);

    /* if we get here okay, then the compact succeeded */
    r = compact_closerename(&original, &compact, now);
    if (r) goto error;

    return 0;

error:
    if (in) prot_free(in);
    if (gzuc) gzuc_free(&gzuc);
    if (keep_message_guids) sync_msgid_list_free(&keep_message_guids);
    if (all_chunks) backup_chunk_list_free(&all_chunks);
    if (keep_chunks) backup_chunk_list_free(&keep_chunks);
    if (compact) backup_unlink(&compact);
    if (original) backup_close(&original);

    return r ? r : -1;
}