int capabilities(struct protstream *conn, sasl_conn_t *saslconn, int starttls_done, int authenticated, sasl_ssf_t sasl_ssf) { const char *sasllist; int mechcount; /* implementation */ prot_printf(conn, "\"IMPLEMENTATION\" \"Cyrus timsieved%s %s\"\r\n", config_mupdate_server ? " (Murder)" : "", cyrus_version()); /* SASL */ if ((!authenticated || sasl_ssf) && sasl_listmech(saslconn, NULL, "\"SASL\" \"", " ", "\"\r\n", &sasllist, NULL, &mechcount) == SASL_OK/* && mechcount > 0*/) { prot_printf(conn,"%s",sasllist); } /* Sieve capabilities */ prot_printf(conn,"\"SIEVE\" \"%s\"\r\n",sieve_listextensions(interp)); if (tls_enabled() && !starttls_done && !authenticated) { prot_printf(conn, "\"STARTTLS\"\r\n"); } prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; }
/* Proxy GETANNOTATION commands to backend */ int annotate_fetch_proxy(const char *server, const char *mbox_pat, const strarray_t *entry_pat, const strarray_t *attribute_pat) { struct backend *be; int i; char mytag[128]; assert(server && mbox_pat && entry_pat && attribute_pat); be = proxy_findserver(server, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); if (!be) return IMAP_SERVER_UNAVAILABLE; /* Send command to remote */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(be->out, "%s GETANNOTATION \"%s\" (", mytag, mbox_pat); for (i = 0 ; i < entry_pat->count ; i++) { prot_printf(be->out, "%s\"%s\"", i ? " " : "", entry_pat->data[i]); } prot_printf(be->out, ") ("); for (i = 0 ; i < attribute_pat->count ; i++) { prot_printf(be->out, "%s\"%s\"", i ? " " : "", attribute_pat->data[i]); } prot_printf(be->out, ")\r\n"); prot_flush(be->out); /* Pipe the results. Note that backend-current may also pipe us other messages. */ pipe_until_tag(be, mytag, 0); return 0; }
static int ping(struct backend *s, const char *userid) { unsigned code = 0; const char *errstr; hdrcache_t resp_hdrs = NULL; struct body_t resp_body; /* Send Authorization request to server */ prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n"); prot_printf(s->out, "Host: %s\r\n", s->hostname); prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); prot_printf(s->out, "Authorize-As: %s\r\n", userid ? userid : "anonymous"); prot_puts(s->out, "\r\n"); prot_flush(s->out); /* Read response(s) from backend until final response or error */ do { resp_body.flags = BODY_DISCARD; if (http_read_response(s, METH_OPTIONS, &code, NULL, &resp_hdrs, &resp_body, &errstr)) { break; } } while (code < 200); if (resp_hdrs) spool_free_hdrcache(resp_hdrs); return (code != 200); }
int installdata(int version,struct protstream *pout, struct protstream *pin, char *scriptname, char *data, int len, char **refer_to, char **errstrp) { int res; int ret; char *errstr=NULL; lexstate_t state; prot_printf(pout, "PUTSCRIPT \"%s\" ",scriptname); prot_printf(pout, "{%d+}\r\n",len); prot_write(pout, data, len); prot_printf(pout,"\r\n"); prot_flush(pout); /* now let's see what the server said */ res=yylex(&state,pin); ret = handle_response(res,version,pin,refer_to,&errstr); /* if command failed */ if(ret == -2 && *refer_to) { return -2; } else if (ret!=0) { *errstrp = strconcat("Putting script: ", errstr, (char *)NULL); return -1; } return 0; }
static int do_restart() { static int restartcnt = 0; if (sync_out->userdata) { /* IMAP flavor (w/ tag) */ prot_printf(sync_out, "R%d SYNC", restartcnt++); } prot_printf(sync_out, "RESTART\r\n"); prot_flush(sync_out); return sync_parse_response("RESTART", sync_in, NULL); }
static int do_restart() { static int restartcnt = 0; if (sync_out->userdata) { /* IMAP flavor (w/ tag) */ struct buf *tag = (struct buf *) sync_out->userdata; buf_reset(tag); buf_printf(tag, "R%d", restartcnt++); prot_printf(sync_out, "%s SYNC", buf_cstring(tag)); } prot_printf(sync_out, "RESTART\r\n"); prot_flush(sync_out); return sync_parse_response("RESTART", sync_in, NULL); }
int setscriptactive(int version, struct protstream *pout, struct protstream *pin,char *name, char **refer_to, char **errstrp) { lexstate_t state; int res; int ret; char *errstr=NULL; /* tell server we want "name" to be the active script */ prot_printf(pout, "SETACTIVE \"%s\"\r\n",name); prot_flush(pout); /* now let's see what the server said */ res=yylex(&state, pin); ret = handle_response(res, version, pin, refer_to, &errstr); /* if command failed */ if(ret == -2 && *refer_to) { return -2; } else if (ret != 0) { *errstrp = strconcat("Setting script active: ", errstr, (char *)NULL); return -1; } return 0; }
EXPORTED int backend_ping(struct backend *s, const char *userid) { char buf[1024]; struct simple_cmd_t *ping_cmd; if (!s) return 0; if (s->sock == -1) return -1; /* Disconnected Socket */ if (s->prot->type == TYPE_SPEC) return s->prot->u.spec.ping(s, userid); ping_cmd = &s->prot->u.std.ping_cmd; if (!ping_cmd->cmd) return 0; prot_printf(s->out, "%s\r\n", ping_cmd->cmd); prot_flush(s->out); for (;;) { if (!prot_fgets(buf, sizeof(buf), s->in)) { /* connection closed? */ return -1; } else if (ping_cmd->unsol && !strncmp(ping_cmd->unsol, buf, strlen(ping_cmd->unsol))) { /* unsolicited response */ continue; } else { /* success/fail response */ return strncmp(ping_cmd->ok, buf, strlen(ping_cmd->ok)); } } }
int deleteascript(int version, struct protstream *pout, struct protstream *pin, const char *name, char **refer_to, char **errstrp) { lexstate_t state; int res; int ret; char *errstr = NULL; prot_printf(pout,"DELETESCRIPT \"%s\"\r\n",name); prot_flush(pout); res=yylex(&state, pin); ret = handle_response(res,version,pin,refer_to,&errstr); if(ret == -2 && *refer_to) { return -2; } else if (ret!=0) { *errstrp = strconcat("Deleting script: ", errstr, (char *)NULL); return -1; } return 0; }
int backend_ping(struct backend *s) { char buf[1024]; if (!s || !s->prot->ping_cmd.cmd) return 0; if (s->sock == -1) return -1; /* Disconnected Socket */ prot_printf(s->out, "%s\r\n", s->prot->ping_cmd.cmd); prot_flush(s->out); for (;;) { if (!prot_fgets(buf, sizeof(buf), s->in)) { /* connection closed? */ return -1; } else if (s->prot->ping_cmd.unsol && !strncmp(s->prot->ping_cmd.unsol, buf, strlen(s->prot->ping_cmd.unsol))) { /* unsolicited response */ continue; } else { /* success/fail response */ return strncmp(s->prot->ping_cmd.ok, buf, strlen(s->prot->ping_cmd.ok)); } } }
int detect_mitm(isieve_t *obj, char *mechlist) { char *new_mechlist; int ch, r = 0; /* wait and probe for possible automatic capability response */ usleep(250000); prot_NONBLOCK(obj->pin); if ((ch = prot_getc(obj->pin)) != EOF) { /* automatic capability response */ prot_ungetc(ch, obj->pin); } else { /* manually ask for capabilities */ prot_printf(obj->pout, "CAPABILITY\r\n"); prot_flush(obj->pout); } prot_BLOCK(obj->pin); if ((new_mechlist = read_capability(obj))) { /* if the server still advertises SASL mechs, compare lists */ r = strcmp(new_mechlist, mechlist); free(new_mechlist); } return r; }
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); }
int setactive(struct protstream *conn, mystring_t *name) { int result; char filename[1024]; /* if string name is empty, disable active script */ if (!strlen(string_DATAPTR(name))) { if (deleteactive(conn) != TIMSIEVE_OK) return TIMSIEVE_FAIL; prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; } result = scriptname_valid(name); if (result!=TIMSIEVE_OK) { prot_printf(conn,"NO \"Invalid script name\"\r\n"); return result; } if (exists(string_DATAPTR(name))==FALSE) { prot_printf(conn,"NO \"Script does not exist\"\r\n"); return TIMSIEVE_NOEXIST; } /* if script already is the active one just say ok */ if (isactive(string_DATAPTR(name))==TRUE) { prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; } /* get the name of the active sieve script */ snprintf(filename, sizeof(filename), "%s.bc", string_DATAPTR(name)); /* ok we want to do this atomically so let's - make <activesieve>.NEW as a hard link - rename it to <activesieve> */ result = symlink(filename, "defaultbc.NEW"); if (result) { syslog(LOG_ERR, "symlink(%s, defaultbc.NEW): %m", filename); prot_printf(conn, "NO \"Can't make link\"\r\n"); return TIMSIEVE_FAIL; } result = rename("defaultbc.NEW", "defaultbc"); if (result) { unlink("defaultbc.NEW"); syslog(LOG_ERR, "rename(defaultbc.NEW, defaultbc): %m"); prot_printf(conn,"NO \"Error renaming\"\r\n"); return TIMSIEVE_FAIL; } sync_log_sieve(sieved_userid); prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; }
static int deleteactive(struct protstream *conn) { if (unlink("defaultbc") != 0) { prot_printf(conn,"NO \"Unable to unlink active script\"\r\n"); return TIMSIEVE_FAIL; } sync_log_sieve(sieved_userid); return TIMSIEVE_OK; }
/* * Get capabilities from the server, and parse them according to * details in the protocol_t, so that the CAPA() macro and perhaps * the backend_get_cap_params() function will notice them. Any * capabilities previously parsed are forgotten. * * The server might give us capabilities for free just because we * connected (or did a STARTTLS or logged in); in this case, call * with a non-zero value for @automatic. Otherwise, we send a * protocol-specific command to the server to tickle it into * disgorging some capabilities. * * Returns: 1 if any capabilities were found, 0 otherwise. */ static int ask_capability(struct backend *s, int dobanner, int automatic) { struct protstream *pout = s->out, *pin = s->in; const struct protocol_t *prot = s->prot; int matches = 0; char str[4096]; const char *resp; resp = (automatic == AUTO_CAPA_BANNER) ? prot->banner.resp : prot->capa_cmd.resp; if (!automatic) { /* no capability command */ if (!prot->capa_cmd.cmd) return -1; /* request capabilities of server */ prot_printf(pout, "%s", prot->capa_cmd.cmd); if (prot->capa_cmd.arg) prot_printf(pout, " %s", prot->capa_cmd.arg); prot_printf(pout, "\r\n"); prot_flush(pout); } forget_capabilities(s); do { if (prot_fgets(str, sizeof(str), pin) == NULL) break; matches |= parse_capability(s, str); if (!resp) { /* multiline response with no distinct end (IMAP banner) */ prot_NONBLOCK(pin); } if (dobanner) strncpy(s->banner, str, sizeof(s->banner)); /* look for the end of the capabilities */ } while (!resp || strncasecmp(str, resp, strlen(resp))); prot_BLOCK(pin); post_parse_capability(s); return matches; }
/* list the scripts user has available */ int listscripts(struct protstream *conn) { DIR *dp; struct dirent *dir; size_t length; /* open the directory */ dp=opendir("."); if (dp==NULL) { prot_printf(conn,"NO \"Error opening directory\"\r\n"); return TIMSIEVE_FAIL; } while ((dir=readdir(dp)) != NULL) /* while there are files here */ { length=strlen(dir->d_name); if (length >= strlen(".script")) /* if ends in .script */ { if (strcmp(dir->d_name + (length - 7), ".script")==0) { char *namewo=(char *) xmalloc(length-6); memcpy(namewo, dir->d_name, length-7); namewo[length-7]='\0'; if (isactive(namewo)==TRUE) prot_printf(conn,"\"%s\" ACTIVE\r\n", namewo); else prot_printf(conn,"\"%s\"\r\n", namewo); free(namewo); } } } closedir(dp); prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; }
int cmd_havespace(struct protstream *conn, mystring_t *sieve_name, unsigned long num) { int result; int maxscripts; unsigned long maxscriptsize; result = scriptname_valid(sieve_name); if (result!=TIMSIEVE_OK) { prot_printf(conn,"NO \"Invalid script name\"\r\n"); return result; } /* see if the size of the script is too big */ maxscriptsize = config_getint(IMAPOPT_SIEVE_MAXSCRIPTSIZE); maxscriptsize *= 1024; if (num > maxscriptsize) { prot_printf(conn, "NO (\"QUOTA\") \"Script size is too large. " "Max script size is %ld bytes\"\r\n", maxscriptsize); return TIMSIEVE_FAIL; } /* see if this would put the user over quota */ maxscripts = config_getint(IMAPOPT_SIEVE_MAXSCRIPTS); if (countscripts(string_DATAPTR(sieve_name))+1 > maxscripts) { prot_printf(conn, "NO (\"QUOTA\") \"You are only allowed %d scripts on this server\"\r\n", maxscripts); return TIMSIEVE_FAIL; } prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; }
void fatal(const char* s, int code) { static int recurse_code = 0; if(recurse_code) exit(code); else recurse_code = 0; prot_printf(deliver_out,"421 4.3.0 deliver: %s\r\n", s); prot_flush(deliver_out); cyrus_done(); exit(code); }
int list_wcb(int version, struct protstream *pout, struct protstream *pin,isieve_listcb_t *cb ,void *rock, char **refer_to) { lexstate_t state; int end=0; int res; int ret = 0; prot_printf(pout, "LISTSCRIPTS\r\n"); prot_flush(pout); do { if ((res=yylex(&state, pin))==STRING) { char *str=state.str; if (yylex(&state, pin)==' ') { if (yylex(&state, pin)!=TOKEN_ACTIVE) printf("Expected ACTIVE\n"); if (yylex(&state, pin)!=EOL) printf("Expected EOL\n"); cb(str, 1, rock); } else { /* in old version we had that '*' means active script thing */ if (version == OLD_VERSION) { if (str[strlen(str)-1]=='*') { str[strlen(str)-1]='\0'; cb(str, 1, rock); } else { cb(str, 0, rock); } } else { /* NEW_VERSION */ /* assume it's a EOL */ cb(str, 0, rock); } } } else { ret = handle_response(res,version,pin,refer_to,NULL); end=1; } } while (end==0); return ret; }
int isieve_logout(isieve_t **obj) { prot_printf((*obj)->pout, "LOGOUT"); prot_flush((*obj)->pout); close((*obj)->sock); sieve_free_net(*obj); *obj = NULL; return STAT_OK; }
static void printfile(struct protstream *out, const struct dlist *dl) { struct stat sbuf; FILE *f; unsigned long size; struct message_guid guid2; const char *msg_base = NULL; size_t msg_len = 0; assert(dlist_isfile(dl)); f = fopen(dl->sval, "r"); if (!f) { syslog(LOG_ERR, "IOERROR: Failed to read file %s", dl->sval); prot_printf(out, "NIL"); return; } if (fstat(fileno(f), &sbuf) == -1) { syslog(LOG_ERR, "IOERROR: Failed to stat file %s", dl->sval); prot_printf(out, "NIL"); fclose(f); return; } size = sbuf.st_size; if (size != dl->nval) { syslog(LOG_ERR, "IOERROR: Size mismatch %s (%lu != " MODSEQ_FMT ")", dl->sval, size, dl->nval); prot_printf(out, "NIL"); fclose(f); return; } map_refresh(fileno(f), 1, &msg_base, &msg_len, sbuf.st_size, "new message", 0); message_guid_generate(&guid2, msg_base, msg_len); if (!message_guid_equal(&guid2, dl->gval)) { syslog(LOG_ERR, "IOERROR: GUID mismatch %s", dl->sval); prot_printf(out, "NIL"); fclose(f); map_free(&msg_base, &msg_len); return; } prot_printf(out, "%%{"); prot_printastring(out, dl->part); prot_printf(out, " "); prot_printastring(out, message_guid_encode(dl->gval)); prot_printf(out, " %lu}\r\n", size); prot_write(out, msg_base, msg_len); fclose(f); map_free(&msg_base, &msg_len); }
int pipe_including_tag(struct backend *s, const char *tag, int force_notfatal) { int r; r = pipe_response(s, tag, 1, force_notfatal); if (r == PROXY_NOCONNECTION) { /* don't have to worry about downing the server, since * pipe_until_tag does that for us */ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE)); } return r; }
/* delete a sieve script */ int deletescript(struct protstream *conn, mystring_t *name) { int result; char path[1024]; result = scriptname_valid(name); if (result!=TIMSIEVE_OK) { prot_printf(conn,"NO \"Invalid script name\"\r\n"); return result; } snprintf(path, 1023, "%s.script", string_DATAPTR(name)); if (isactive(string_DATAPTR(name)) && (deleteactive(conn)!=TIMSIEVE_OK)) { return TIMSIEVE_FAIL; } result = unlink(path); if (result != 0) { prot_printf(conn,"NO \"Error deleting script\"\r\n"); return TIMSIEVE_FAIL; } snprintf(path, 1023, "%s.bc", string_DATAPTR(name)); result = unlink(path); if (result != 0) { prot_printf(conn,"NO \"Error deleting bytecode\"\r\n"); return TIMSIEVE_FAIL; } sync_log_sieve(sieved_userid); prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; }
EXPORTED int backend_starttls( struct backend *s, struct tls_cmd_t *tls_cmd, const char *c_cert_file, const char *c_key_file) { #ifndef HAVE_SSL return -1; #else char *auth_id = NULL; int *layerp = NULL; int r = 0; if (tls_cmd) { char buf[2048]; /* send starttls command */ prot_printf(s->out, "%s\r\n", tls_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, tls_cmd->ok, strlen(tls_cmd->ok))) return -1; } r = tls_init_clientengine(5, c_cert_file, c_key_file); if (r == -1) return -1; /* SASL and openssl have different ideas about whether ssf is signed */ layerp = (int *) &s->ext_ssf; r = tls_start_clienttls(s->in->fd, s->out->fd, layerp, &auth_id, &s->tlsconn, &s->tlssess); if (r == -1) return -1; if (s->saslconn) { r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &s->ext_ssf); if (r == SASL_OK) r = sasl_setprop(s->saslconn, SASL_AUTH_EXTERNAL, auth_id); if (auth_id) free(auth_id); if (r != SASL_OK) return -1; } prot_settls(s->in, s->tlsconn); prot_settls(s->out, s->tlsconn); ask_capability(s, /*dobanner*/1, tls_cmd->auto_capa); return 0; #endif /* HAVE_SSL */ }
EXPORTED void fatal(const char *s, int code) { static int recurse_code = 0; if (recurse_code) { /* We were called recursively. Just give up */ exit(recurse_code); } recurse_code = code; prot_printf(sieved_out, "NO Fatal error: %s\r\n", s); prot_flush(sieved_out); shut_down(EC_TEMPFAIL); }
static int deleteactive(struct protstream *conn) { if (unlink("defaultbc") != 0) { if (errno == ENOENT) { /* RFC 5804, 2.8 SETACTIVE Command: * Disabling an active script when there is no script active is * not an error and MUST result in an OK reply. */ return TIMSIEVE_OK; } prot_printf(conn,"NO \"Unable to unlink active script\"\r\n"); return TIMSIEVE_FAIL; } sync_log_sieve(sieved_userid); return TIMSIEVE_OK; }
static int do_starttls(struct backend *s) { #ifndef HAVE_SSL return -1; #else const struct tls_cmd_t *tls_cmd = &s->prot->tls_cmd; char buf[2048]; int r; int *layerp; char *auth_id; sasl_ssf_t ssf; /* send starttls command */ prot_printf(s->out, "%s\r\n", tls_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, tls_cmd->ok, strlen(tls_cmd->ok))) return -1; r = tls_init_clientengine(5, "", ""); if (r == -1) return -1; /* SASL and openssl have different ideas about whether ssf is signed */ layerp = (int *) &ssf; r = tls_start_clienttls(s->in->fd, s->out->fd, layerp, &auth_id, &s->tlsconn, &s->tlssess); if (r == -1) return -1; r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &ssf); if (r == SASL_OK) r = sasl_setprop(s->saslconn, SASL_AUTH_EXTERNAL, auth_id); if (auth_id) free(auth_id); if (r != SASL_OK) return -1; prot_settls(s->in, s->tlsconn); prot_settls(s->out, s->tlsconn); ask_capability(s, /*dobanner*/1, s->prot->tls_cmd.auto_capa); return 0; #endif /* HAVE_SSL */ }
EXPORTED void fatal(const char* s, int code) { static int recurse_code = 0; if (recurse_code) { /* We were called recursively. Just give up */ proc_cleanup(); exit(recurse_code); } recurse_code = code; open_backups_list_close(&backupd_open_backups, 0); if (backupd_out) { prot_printf(backupd_out, "* Fatal error: %s\r\n", s); prot_flush(backupd_out); } syslog(LOG_ERR, "Fatal error: %s", s); shut_down(code); }
static int do_compress(struct backend *s, struct simple_cmd_t *compress_cmd) { #ifndef HAVE_ZLIB return -1; #else char buf[1024]; /* send compress command */ prot_printf(s->out, "%s\r\n", compress_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, compress_cmd->ok, strlen(compress_cmd->ok))) return -1; prot_setcompress(s->in); prot_setcompress(s->out); return 0; #endif /* HAVE_ZLIB */ }
/* Proxy SETANNOTATION commands to backend */ int annotate_store_proxy(const char *server, const char *mbox_pat, struct entryattlist *entryatts) { struct backend *be; struct entryattlist *e; struct attvaluelist *av; char mytag[128]; assert(server && mbox_pat && entryatts); be = proxy_findserver(server, &imap_protocol, proxy_userid, &backend_cached, &backend_current, &backend_inbox, imapd_in); if (!be) return IMAP_SERVER_UNAVAILABLE; /* Send command to remote */ proxy_gentag(mytag, sizeof(mytag)); prot_printf(be->out, "%s SETANNOTATION \"%s\" (", mytag, mbox_pat); for (e = entryatts; e; e = e->next) { prot_printf(be->out, "\"%s\" (", e->entry); for (av = e->attvalues; av; av = av->next) { prot_printf(be->out, "\"%s\" ", av->attrib); prot_printmap(be->out, av->value.s, av->value.len); prot_printf(be->out, "%s", av->next ? " " : ""); } prot_printf(be->out, ")"); if (e->next) prot_printf(be->out, " "); } prot_printf(be->out, ")\r\n"); prot_flush(be->out); /* Pipe the results. Note that backend-current may also pipe us other messages. */ pipe_until_tag(be, mytag, 0); return 0; }