PageRenderTime 27ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/cyrus-imapd-2.4.16/imap/spool.c

#
C | 491 lines | 312 code | 56 blank | 123 comment | 151 complexity | b0a70faa332e3bdac4984b0b307ac633 MD5 | raw file
  1. /* spool.c -- Routines for spooling/parsing messages from a prot stream
  2. *
  3. * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in
  14. * the documentation and/or other materials provided with the
  15. * distribution.
  16. *
  17. * 3. The name "Carnegie Mellon University" must not be used to
  18. * endorse or promote products derived from this software without
  19. * prior written permission. For permission or any legal
  20. * details, please contact
  21. * Carnegie Mellon University
  22. * Center for Technology Transfer and Enterprise Creation
  23. * 4615 Forbes Avenue
  24. * Suite 302
  25. * Pittsburgh, PA 15213
  26. * (412) 268-7393, fax: (412) 268-7395
  27. * innovation@andrew.cmu.edu
  28. *
  29. * 4. Redistributions of any form whatsoever must retain the following
  30. * acknowledgment:
  31. * "This product includes software developed by Computing Services
  32. * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
  33. *
  34. * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
  35. * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  36. * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
  37. * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  38. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  39. * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
  40. * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41. *
  42. * $Id: spool.c,v 1.16 2010/01/06 17:01:40 murch Exp $
  43. */
  44. #include <config.h>
  45. #ifdef HAVE_UNISTD_H
  46. #include <unistd.h>
  47. #endif
  48. #include <stdio.h>
  49. #include <string.h>
  50. #include <ctype.h>
  51. #include "assert.h"
  52. #include "spool.h"
  53. #include "prot.h"
  54. #include "util.h"
  55. #include "xmalloc.h"
  56. #include "exitcodes.h"
  57. #include "imap_err.h"
  58. #include "nntp_err.h"
  59. #include "global.h"
  60. #define HEADERCACHESIZE 4009
  61. typedef struct Header header_t;
  62. /* data per message */
  63. struct Header {
  64. char *name;
  65. int ncontents;
  66. char *contents[1];
  67. };
  68. hdrcache_t spool_new_hdrcache()
  69. {
  70. int i;
  71. hdrcache_t cache;
  72. cache = (hdrcache_t) xmalloc(HEADERCACHESIZE * sizeof(header_t*));
  73. if (!cache) return NULL;
  74. for (i = 0; i < HEADERCACHESIZE; i++)
  75. cache[i] = NULL;
  76. return cache;
  77. }
  78. /* hash function used for header cache in struct msg */
  79. static int hashheader(char *header)
  80. {
  81. int x = 0;
  82. /* any CHAR except ' ', :, or a ctrl char */
  83. for (; !Uiscntrl(*header) && (*header != ' ') && (*header != ':');
  84. header++) {
  85. x *= 256;
  86. x += *header;
  87. x %= HEADERCACHESIZE;
  88. }
  89. return x;
  90. }
  91. /* take a list of headers, pull the first one out and return it in
  92. name and contents.
  93. copies fin to fout, massaging
  94. returns 0 on success, negative on failure */
  95. typedef enum {
  96. NAME_START,
  97. NAME,
  98. COLON,
  99. BODY_START,
  100. BODY
  101. } state;
  102. enum {
  103. NAMEINC = 128,
  104. BODYINC = 1024
  105. };
  106. /* we don't have to worry about dotstuffing here, since it's illegal
  107. for a header to begin with a dot!
  108. returns 0 on success, filling in 'headname' and 'contents' with a static
  109. pointer (blech).
  110. on end of headers, returns 0 with NULL 'headname' and NULL 'contents'
  111. on error, returns < 0
  112. */
  113. static int parseheader(struct protstream *fin, FILE *fout,
  114. char **headname, char **contents,
  115. const char **skipheaders)
  116. {
  117. int c;
  118. static char *name = NULL, *body = NULL;
  119. static int namelen = 0, bodylen = 0;
  120. int off = 0;
  121. state s = NAME_START;
  122. int r = 0;
  123. int reject8bit = config_getswitch(IMAPOPT_REJECT8BIT);
  124. int munge8bit = config_getswitch(IMAPOPT_MUNGE8BIT);
  125. const char **skip = NULL;
  126. if (namelen == 0) {
  127. namelen += NAMEINC;
  128. name = (char *) xrealloc(name, namelen * sizeof(char));
  129. }
  130. if (bodylen == 0) {
  131. bodylen += BODYINC;
  132. body = (char *) xrealloc(body, bodylen * sizeof(char));
  133. }
  134. /* there are two ways out of this loop, both via gotos:
  135. either we successfully read a header (got_header)
  136. or we hit an error (ph_error) */
  137. while ((c = prot_getc(fin)) != EOF) { /* examine each character */
  138. /* reject \0 */
  139. if (!c) {
  140. r = IMAP_MESSAGE_CONTAINSNULL;
  141. goto ph_error;
  142. }
  143. switch (s) {
  144. case NAME_START:
  145. if (c == '.') {
  146. int peek;
  147. peek = prot_getc(fin);
  148. prot_ungetc(peek, fin);
  149. if (peek == '\r' || peek == '\n') {
  150. /* just reached the end of message */
  151. r = 0;
  152. goto ph_error;
  153. }
  154. }
  155. if (c == '\r' || c == '\n') {
  156. /* just reached the end of headers */
  157. r = 0;
  158. goto ph_error;
  159. }
  160. /* field-name = 1*ftext
  161. ftext = %d33-57 / %d59-126
  162. ; Any character except
  163. ; controls, SP, and
  164. ; ":". */
  165. if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
  166. /* invalid header name */
  167. r = IMAP_MESSAGE_BADHEADER;
  168. goto ph_error;
  169. }
  170. name[0] = c;
  171. off = 1;
  172. s = NAME;
  173. break;
  174. case NAME:
  175. if (c == ' ' || c == '\t' || c == ':') {
  176. name[off] = '\0';
  177. /* see if this header is in our skip list */
  178. for (skip = skipheaders;
  179. skip && *skip && strcasecmp(name, *skip); skip++);
  180. if (!skip || !*skip) {
  181. /* write the header name to the output */
  182. if (fout) fputs(name, fout);
  183. skip = NULL;
  184. }
  185. s = (c == ':' ? BODY_START : COLON);
  186. break;
  187. }
  188. if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
  189. r = IMAP_MESSAGE_BADHEADER;
  190. goto ph_error;
  191. }
  192. name[off++] = c;
  193. if (off >= namelen - 3) {
  194. namelen += NAMEINC;
  195. name = (char *) xrealloc(name, namelen);
  196. }
  197. break;
  198. case COLON:
  199. if (c == ':') {
  200. s = BODY_START;
  201. } else if (c != ' ' && c != '\t') {
  202. /* i want to avoid confusing dot-stuffing later */
  203. while (c == '.') {
  204. if (fout && !skip) fputc(c, fout);
  205. c = prot_getc(fin);
  206. }
  207. r = IMAP_MESSAGE_BADHEADER;
  208. goto ph_error;
  209. }
  210. break;
  211. case BODY_START:
  212. if (c == ' ' || c == '\t') /* eat the whitespace */
  213. break;
  214. off = 0;
  215. s = BODY;
  216. /* falls through! */
  217. case BODY:
  218. /* now we want to convert all newlines into \r\n */
  219. if (c == '\r' || c == '\n') {
  220. int peek;
  221. peek = prot_getc(fin);
  222. if (fout && !skip) {
  223. fputc('\r', fout);
  224. fputc('\n', fout);
  225. }
  226. /* we should peek ahead to see if it's folded whitespace */
  227. if (c == '\r' && peek == '\n') {
  228. c = prot_getc(fin);
  229. } else {
  230. c = peek; /* single newline seperator */
  231. }
  232. if (c != ' ' && c != '\t') {
  233. /* this is the end of the header */
  234. body[off] = '\0';
  235. prot_ungetc(c, fin);
  236. goto got_header;
  237. }
  238. /* ignore this whitespace, but we'll copy all the rest in */
  239. break;
  240. } else {
  241. if (c >= 0x80) {
  242. if (reject8bit) {
  243. /* We have been configured to reject all mail of this
  244. form. */
  245. r = IMAP_MESSAGE_CONTAINS8BIT;
  246. goto ph_error;
  247. } else if (munge8bit) {
  248. /* We have been configured to munge all mail of this
  249. form. */
  250. c = 'X';
  251. }
  252. }
  253. /* just an ordinary character */
  254. body[off++] = c;
  255. if (off >= bodylen - 3) {
  256. bodylen += BODYINC;
  257. body = (char *) xrealloc(body, bodylen);
  258. }
  259. }
  260. }
  261. /* copy this to the output */
  262. if (fout && s != NAME && !skip) fputc(c, fout);
  263. }
  264. /* if we fall off the end of the loop, we hit some sort of error
  265. condition */
  266. ph_error:
  267. /* put the last character back; we'll copy it later */
  268. if (c != EOF) prot_ungetc(c, fin);
  269. /* and we didn't get a header */
  270. if (headname != NULL) *headname = NULL;
  271. if (contents != NULL) *contents = NULL;
  272. return r;
  273. got_header:
  274. if (headname != NULL) *headname = xstrdup(name);
  275. if (contents != NULL) *contents = xstrdup(body);
  276. return 0;
  277. }
  278. void spool_cache_header(char *name, char *body, hdrcache_t cache)
  279. {
  280. int cl, clinit;
  281. lcase(name);
  282. clinit = cl = hashheader(name);
  283. while (cache[cl] != NULL && strcmp(name, cache[cl]->name)) {
  284. cl++; /* resolve collisions linearly */
  285. cl %= HEADERCACHESIZE;
  286. if (cl == clinit) break; /* gone all the way around, so bail */
  287. }
  288. /* found where to put it, so insert it into a list */
  289. if (cache[cl]) {
  290. /* add this body on */
  291. cache[cl]->contents[cache[cl]->ncontents++] = body;
  292. /* whoops, won't have room for the null at the end! */
  293. if (!(cache[cl]->ncontents % 8)) {
  294. /* increase the size */
  295. cache[cl] = (header_t *)
  296. xrealloc(cache[cl],sizeof(header_t) +
  297. ((8 + cache[cl]->ncontents) * sizeof(char *)));
  298. }
  299. /* have no need of this */
  300. free(name);
  301. } else {
  302. /* create a new entry in the hash table */
  303. cache[cl] = (header_t *) xmalloc(sizeof(header_t) + 8 * sizeof(char*));
  304. cache[cl]->name = name;
  305. cache[cl]->contents[0] = body;
  306. cache[cl]->ncontents = 1;
  307. }
  308. /* we always want a NULL at the end */
  309. cache[cl]->contents[cache[cl]->ncontents] = NULL;
  310. }
  311. int spool_fill_hdrcache(struct protstream *fin, FILE *fout, hdrcache_t cache,
  312. const char **skipheaders)
  313. {
  314. int r = 0;
  315. /* let's fill that header cache */
  316. for (;;) {
  317. char *name = NULL, *body = NULL;
  318. if ((r = parseheader(fin, fout, &name, &body, skipheaders)) < 0) {
  319. break;
  320. }
  321. if (!name) {
  322. /* reached the end of headers */
  323. free(body);
  324. break;
  325. }
  326. /* put it in the hash table */
  327. spool_cache_header(name, body, cache);
  328. }
  329. return r;
  330. }
  331. const char **spool_getheader(hdrcache_t cache, const char *phead)
  332. {
  333. char *head;
  334. const char **ret = NULL;
  335. int clinit, cl;
  336. assert(cache && phead);
  337. head = xstrdup(phead);
  338. lcase(head);
  339. /* check the cache */
  340. clinit = cl = hashheader(head);
  341. while (cache[cl] != NULL) {
  342. if (!strcmp(head, cache[cl]->name)) {
  343. ret = (const char **) cache[cl]->contents;
  344. break;
  345. }
  346. cl++; /* try next hash bin */
  347. cl %= HEADERCACHESIZE;
  348. if (cl == clinit) break; /* gone all the way around */
  349. }
  350. free(head);
  351. return ret;
  352. }
  353. void spool_free_hdrcache(hdrcache_t cache)
  354. {
  355. int i, j;
  356. if (!cache) return;
  357. for (i = 0; i < HEADERCACHESIZE; i++) {
  358. if (cache[i]) {
  359. free(cache[i]->name);
  360. for (j = 0; j < cache[i]->ncontents; j++) {
  361. free(cache[i]->contents[j]);
  362. }
  363. free(cache[i]);
  364. }
  365. }
  366. free(cache);
  367. }
  368. /* copies the message from fin to fout, massaging accordingly:
  369. . newlines are fiddled to \r\n
  370. . "." terminates
  371. . embedded NULs are rejected
  372. . bare \r are removed
  373. */
  374. int spool_copy_msg(struct protstream *fin, FILE *fout)
  375. {
  376. char buf[8192], *p;
  377. int r = 0;
  378. /* -2: Might need room to add a \r\n\0 set */
  379. while (prot_fgets(buf, sizeof(buf)-2, fin)) {
  380. p = buf + strlen(buf) - 1;
  381. if (p < buf) {
  382. /* buffer start with a \0 */
  383. r = IMAP_MESSAGE_CONTAINSNULL;
  384. continue; /* need to eat the rest of the message */
  385. }
  386. else if (buf[0] == '\r' && buf[1] == '\0') {
  387. /* The message contained \r\0, and fgets is confusing us. */
  388. r = IMAP_MESSAGE_CONTAINSNULL;
  389. continue; /* need to eat the rest of the message */
  390. }
  391. else if (p[0] == '\r') {
  392. /*
  393. * We were unlucky enough to get a CR just before we ran
  394. * out of buffer--put it back.
  395. */
  396. prot_ungetc('\r', fin);
  397. *p = '\0';
  398. }
  399. else if (p[0] == '\n' && (p == buf || p[-1] != '\r')) {
  400. /* found an \n without a \r */
  401. p[0] = '\r';
  402. p[1] = '\n';
  403. p[2] = '\0';
  404. }
  405. else if (p[0] != '\n' && (strlen(buf) < sizeof(buf)-3)) {
  406. /* line contained a \0 not at the end */
  407. r = IMAP_MESSAGE_CONTAINSNULL;
  408. continue;
  409. }
  410. /* Remove any lone CR characters */
  411. while ((p = strchr(buf, '\r')) && p[1] != '\n') {
  412. /* Src/Target overlap, use memmove */
  413. /* strlen(p) will result in copying the NUL byte as well */
  414. memmove(p, p+1, strlen(p));
  415. }
  416. if (buf[0] == '.') {
  417. if (buf[1] == '\r' && buf[2] == '\n') {
  418. /* End of message */
  419. goto dot;
  420. }
  421. /* Remove the dot-stuffing */
  422. if (fout) fputs(buf+1, fout);
  423. } else {
  424. if (fout) fputs(buf, fout);
  425. }
  426. }
  427. /* wow, serious error---got a premature EOF. */
  428. return IMAP_IOERROR;
  429. dot:
  430. return r;
  431. }