/* This handles piping of the LSUB command, because we have to figure out * what mailboxes actually exist before passing them to the end user. * * It is also needed if we are doing a FIND MAILBOXES, for that we do an * LSUB on the backend anyway, because the semantics of FIND do not allow * it to return nonexistant mailboxes (RFC1176), but we need to really dumb * down the response when this is the case. */ int pipe_lsub(struct backend *s, const char *userid, const char *tag, int force_notfatal, const char *resp) { int taglen = strlen(tag); int c; int r = PROXY_OK; int exist_r; static struct buf tagb, cmd, sep, name; struct buf flags = BUF_INITIALIZER; const char *end_strip_flags[] = { " \\NonExistent)", "\\NonExistent)", " \\Noselect)", "\\Noselect)", NULL }; const char *mid_strip_flags[] = { "\\NonExistent ", "\\Noselect ", NULL }; assert(s); assert(s->timeout); s->timeout->mark = time(NULL) + IDLE_TIMEOUT; while(1) { c = getword(s->in, &tagb); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(!strncmp(tag, tagb.s, taglen)) { char buf[2048]; if(!prot_fgets(buf, sizeof(buf), s->in)) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Got the end of the response */ buf_appendcstr(&s->last_result, buf); buf_cstring(&s->last_result); switch (buf[0]) { case 'O': case 'o': r = PROXY_OK; break; case 'N': case 'n': r = PROXY_NO; break; case 'B': case 'b': r = PROXY_BAD; break; default: /* huh? no result? */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; break; } break; /* we're done */ } c = getword(s->in, &cmd); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(strncasecmp("LSUB", cmd.s, 4) && strncasecmp("LIST", cmd.s, 4)) { prot_printf(imapd_out, "%s %s ", tagb.s, cmd.s); r = pipe_to_end_of_response(s, force_notfatal); if (r != PROXY_OK) goto out; } else { /* build up the response bit by bit */ int i; buf_reset(&flags); c = prot_getc(s->in); while(c != ')' && c != EOF) { buf_putc(&flags, c); c = prot_getc(s->in); } if(c != EOF) { /* terminate string */ buf_putc(&flags, ')'); buf_cstring(&flags); /* get the next character */ c = prot_getc(s->in); } if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Check for flags that we should remove * (e.g. Noselect, NonExistent) */ for(i=0; end_strip_flags[i]; i++) buf_replace_all(&flags, end_strip_flags[i], ")"); for (i=0; mid_strip_flags[i]; i++) buf_replace_all(&flags, mid_strip_flags[i], NULL); /* Get separator */ c = getastring(s->in, s->out, &sep); if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Get name */ c = getastring(s->in, s->out, &name); if(c == '\r') c = prot_getc(s->in); if(c != '\n') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* lookup name */ exist_r = 1; char *intname = mboxname_from_external(name.s, &imapd_namespace, userid); mbentry_t *mbentry = NULL; exist_r = mboxlist_lookup(intname, &mbentry, NULL); free(intname); if(!exist_r && (mbentry->mbtype & MBTYPE_RESERVE)) exist_r = IMAP_MAILBOX_RESERVED; mboxlist_entry_free(&mbentry); /* send our response */ /* we need to set \Noselect if it's not in our mailboxes.db */ if (resp[0] == 'L') { if(!exist_r) { prot_printf(imapd_out, "* %s %s \"%s\" ", resp, flags.s, sep.s); } else { prot_printf(imapd_out, "* %s (\\Noselect) \"%s\" ", resp, sep.s); } prot_printstring(imapd_out, name.s); prot_printf(imapd_out, "\r\n"); } else if(resp[0] == 'M' && !exist_r) { /* Note that it has to exist for a find response */ prot_printf(imapd_out, "* %s ", resp); prot_printastring(imapd_out, name.s); prot_printf(imapd_out, "\r\n"); } } } /* while(1) */ out: buf_free(&flags); return r; }
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; }
/* save name as a sieve script */ int putscript(struct protstream *conn, const struct buf *name, const struct buf *data, int verify_only) { FILE *stream; const char *dataptr; char *errstr; unsigned int i; int last_was_r = 0; int result; char path[1024], p2[1024]; char bc_path[1024], bc_p2[1024]; int maxscripts; sieve_script_t *s; result = scriptname_valid(name); if (result!=TIMSIEVE_OK) { prot_printf(conn,"NO \"Invalid script name\"\r\n"); return result; } if (verify_only) stream = tmpfile(); else { /* see if this would put the user over quota */ maxscripts = config_getint(IMAPOPT_SIEVE_MAXSCRIPTS); if (countscripts(name->s)+1 > maxscripts) { prot_printf(conn, "NO (QUOTA/MAXSCRIPTS) \"You are only allowed %d scripts on this server\"\r\n", maxscripts); return TIMSIEVE_FAIL; } snprintf(path, 1023, "%s.script.NEW", name->s); stream = fopen(path, "w+"); } if (stream == NULL) { prot_printf(conn, "NO \"Unable to open script for writing (%s)\"\r\n", path); return TIMSIEVE_NOEXIST; } dataptr = data->s; /* copy data to file - replacing any lone \r or \n with the * \r\n pair so notify messages are SMTP compatible */ for (i = 0; i < data->len; i++) { if (last_was_r) { if (dataptr[i] != '\n') putc('\n', stream); } else { if (dataptr[i] == '\n') putc('\r', stream); } putc(dataptr[i], stream); last_was_r = (dataptr[i] == '\r'); } if (last_was_r) putc('\n', stream); /* let's make sure this is a valid script (no parse errors) */ result = is_script_parsable(stream, &errstr, &s); if (result != TIMSIEVE_OK) { if (errstr && *errstr) { prot_printf(conn, "NO "); prot_printstring(conn, errstr); prot_printf(conn, "\r\n"); } else { prot_printf(conn, "NO \"parse failed\"\r\n"); } free(errstr); fclose(stream); unlink(path); return result; } fflush(stream); fclose(stream); if (!verify_only) { int fd; bytecode_info_t *bc; /* Now, generate the bytecode */ if(sieve_generate_bytecode(&bc, s) == -1) { unlink(path); sieve_script_free(&s); prot_printf(conn, "NO \"bytecode generate failed\"\r\n"); return TIMSIEVE_FAIL; } /* Now, open the new file */ snprintf(bc_path, 1023, "%s.bc.NEW", name->s); fd = open(bc_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if(fd < 0) { unlink(path); sieve_free_bytecode(&bc); sieve_script_free(&s); prot_printf(conn, "NO \"couldn't open bytecode file\"\r\n"); return TIMSIEVE_FAIL; } /* Now, emit the bytecode */ if(sieve_emit_bytecode(fd, bc) == -1) { close(fd); unlink(path); unlink(bc_path); sieve_free_bytecode(&bc); sieve_script_free(&s); prot_printf(conn, "NO \"bytecode emit failed\"\r\n"); return TIMSIEVE_FAIL; } sieve_free_bytecode(&bc); sieve_script_free(&s); close(fd); /* Now, rename! */ snprintf(p2, 1023, "%s.script", name->s); snprintf(bc_p2, 1023, "%s.bc", name->s); rename(path, p2); rename(bc_path, bc_p2); } prot_printf(conn, "OK\r\n"); sync_log_sieve(sieved_userid); return TIMSIEVE_OK; }