/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * camel-multipart.c : Abstract class for a multipart * * Authors: Michael Zucchi * * Copyright 2002 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_ALLOCA_H #include #endif #include #include #include #include #include #include "camel-mime-part.h" #include "camel-mime-message.h" #include "camel-mime-parser.h" #include "camel-stream-mem.h" #include "camel-multipart-signed.h" #include "camel-mime-part.h" #include "camel-exception.h" #include "md5-utils.h" #include "camel-stream-filter.h" #include "camel-seekable-substream.h" #include "camel-mime-filter-crlf.h" #include "camel-mime-filter-canon.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x)) #include ;*/ static void signed_add_part(CamelMultipart *multipart, CamelMimePart *part); static void signed_add_part_at(CamelMultipart *multipart, CamelMimePart *part, guint index); static void signed_remove_part(CamelMultipart *multipart, CamelMimePart *part); static CamelMimePart *signed_remove_part_at (CamelMultipart *multipart, guint index); static CamelMimePart *signed_get_part(CamelMultipart *multipart, guint index); static guint signed_get_number(CamelMultipart *multipart); static int write_to_stream(CamelDataWrapper *data_wrapper, CamelStream *stream); static void set_mime_type_field(CamelDataWrapper *data_wrapper, CamelContentType *mime_type); static int construct_from_stream(CamelDataWrapper *data_wrapper, CamelStream *stream); static int signed_construct_from_parser(CamelMultipart *multipart, struct _CamelMimeParser *mp); static CamelMultipartClass *parent_class = NULL; /* Returns the class for a CamelMultipartSigned */ #define CMP_CLASS(so) CAMEL_MULTIPART_SIGNED_CLASS (CAMEL_OBJECT_GET_CLASS(so)) /* Returns the class for a CamelDataWrapper */ #define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so)) static void camel_multipart_signed_class_init (CamelMultipartSignedClass *camel_multipart_signed_class) { CamelDataWrapperClass *camel_data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS(camel_multipart_signed_class); CamelMultipartClass *mpclass = (CamelMultipartClass *)camel_multipart_signed_class; parent_class = (CamelMultipartClass *)camel_multipart_get_type(); /* virtual method overload */ camel_data_wrapper_class->construct_from_stream = construct_from_stream; camel_data_wrapper_class->write_to_stream = write_to_stream; camel_data_wrapper_class->set_mime_type_field = set_mime_type_field; mpclass->add_part = signed_add_part; mpclass->add_part_at = signed_add_part_at; mpclass->remove_part = signed_remove_part; mpclass->remove_part_at = signed_remove_part_at; mpclass->get_part = signed_get_part; mpclass->get_number = signed_get_number; mpclass->construct_from_parser = signed_construct_from_parser; /* mpclass->get_boundary = signed_get_boundary; mpclass->set_boundary = signed_set_boundary; */ } static void camel_multipart_signed_init (gpointer object, gpointer klass) { CamelMultipartSigned *multipart = (CamelMultipartSigned *)object; camel_data_wrapper_set_mime_type(CAMEL_DATA_WRAPPER(multipart), "multipart/signed"); multipart->start1 = -1; } static void camel_multipart_signed_finalize (CamelObject *object) { CamelMultipartSigned *mps = (CamelMultipartSigned *)object; g_free(mps->protocol); g_free(mps->micalg); if (mps->signature) camel_object_unref((CamelObject *)mps->signature); if (mps->content) camel_object_unref((CamelObject *)mps->content); if (mps->contentraw) camel_object_unref((CamelObject *)mps->contentraw); } CamelType camel_multipart_signed_get_type (void) { static CamelType camel_multipart_signed_type = CAMEL_INVALID_TYPE; if (camel_multipart_signed_type == CAMEL_INVALID_TYPE) { camel_multipart_signed_type = camel_type_register (camel_multipart_get_type (), "CamelMultipartSigned", sizeof (CamelMultipartSigned), sizeof (CamelMultipartSignedClass), (CamelObjectClassInitFunc) camel_multipart_signed_class_init, NULL, (CamelObjectInitFunc) camel_multipart_signed_init, (CamelObjectFinalizeFunc) camel_multipart_signed_finalize); } return camel_multipart_signed_type; } /** * camel_multipart_signed_new: * * Create a new CamelMultipartSigned object. * * A MultipartSigned should be used to store and create parts of * type "multipart/signed". This is because multipart/signed is * entirely broken-by-design (tm) and uses completely * different semantics to other mutlipart types. It must be treated * as opaque data by any transport. See rfc 3156 for details. * * There are 3 ways to create the part: * Use construct_from_stream. If this is used, then you must * set the mime_type appropriately to match the data uses, so * that the multiple parts my be extracted. * * Use construct_from_parser. The parser MUST be in the HSCAN_HEADER * state, and the current content_type MUST be "multipart/signed" with * the appropriate boundary and it SHOULD include the appropriate protocol * and hash specifiers. * * Use sign_part. A signature part will automatically be created * and the whole part may be written using write_to_stream to * create a 'transport-safe' version (as safe as can be expected with * such a broken specification). * * Return value: a new CamelMultipartSigned **/ CamelMultipartSigned * camel_multipart_signed_new (void) { CamelMultipartSigned *multipart; multipart = (CamelMultipartSigned *)camel_object_new(CAMEL_MULTIPART_SIGNED_TYPE); return multipart; } /* find the next boundary @bound from @start, return the start of the actual data @end points to the end of the data BEFORE the boundary */ static char *parse_boundary(char *start, const char *bound, char **end) { char *data, *begin; begin = strstr(start, bound); if (begin == NULL) return NULL; data = begin+strlen(bound); if (begin > start && begin[-1] == '\n') begin--; if (begin > start && begin[-1] == '\r') begin--; if (data[0] == '\r') data++; if (data[0] == '\n') data++; *end = begin; return data; } /* yeah yuck. Well, we could probably use the normal mime parser, but then it would change our headers. This is good enough ... till its not! */ static int parse_content(CamelMultipartSigned *mps) { CamelMultipart *mp = (CamelMultipart *)mps; char *start, *end, *start2, *end2, *last, *post; CamelStreamMem *mem; char *bound; const char *boundary; boundary = camel_multipart_get_boundary(mp); if (boundary == NULL) { g_warning("Trying to get multipart/signed content without setting boundary first"); return -1; } /* turn it into a string, and 'fix' it up */ /* this is extremely dodgey but should work! */ mem = (CamelStreamMem *)((CamelDataWrapper *)mps)->stream; if (mem == NULL) { g_warning("Trying to parse multipart/signed without constructing first"); return -1; } camel_stream_write((CamelStream *)mem, "", 1); g_byte_array_set_size(mem->buffer, mem->buffer->len-1); last = mem->buffer->data + mem->buffer->len; bound = alloca(strlen(boundary)+5); sprintf(bound, "--%s", boundary); start = parse_boundary(mem->buffer->data, bound, &end); if (start == NULL || start[0] == 0) return -1; if (end > (char *)mem->buffer->data) { char *tmp = g_strndup(mem->buffer->data, start-(char *)mem->buffer->data-1); camel_multipart_set_preface(mp, tmp); g_free(tmp); } start2 = parse_boundary(start, bound, &end); if (start2 == NULL || start2[0] == 0) return -1; sprintf(bound, "--%s--", boundary); post = parse_boundary(start2, bound, &end2); if (post == NULL) return -1; if (post[0]) camel_multipart_set_postface(mp, post); mps->start1 = start-(char *)mem->buffer->data; mps->end1 = end-(char *)mem->buffer->data; mps->start2 = start2-(char *)mem->buffer->data; mps->end2 = end2-(char *)mem->buffer->data; return 0; } /* we snoop the mime type to get boundary and hash info */ static void set_mime_type_field(CamelDataWrapper *data_wrapper, CamelContentType *mime_type) { CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper; ((CamelDataWrapperClass *)parent_class)->set_mime_type_field(data_wrapper, mime_type); if (mime_type) { const char *micalg, *protocol; protocol = header_content_type_param(mime_type, "protocol"); g_free(mps->protocol); mps->protocol = g_strdup(protocol); micalg = header_content_type_param(mime_type, "micalg"); g_free(mps->micalg); mps->micalg = g_strdup(micalg); } } static void signed_add_part(CamelMultipart *multipart, CamelMimePart *part) { g_warning("Cannot add parts to a signed part using add_part"); } static void signed_add_part_at(CamelMultipart *multipart, CamelMimePart *part, guint index) { g_warning("Cannot add parts to a signed part using add_part_at"); } static void signed_remove_part(CamelMultipart *multipart, CamelMimePart *part) { g_warning("Cannot remove parts from a signed part using remove_part"); } static CamelMimePart * signed_remove_part_at (CamelMultipart *multipart, guint index) { g_warning("Cannot remove parts from a signed part using remove_part"); return NULL; } static CamelMimePart * signed_get_part(CamelMultipart *multipart, guint index) { CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart; CamelDataWrapper *dw = (CamelDataWrapper *)multipart; CamelStream *stream; switch (index) { case CAMEL_MULTIPART_SIGNED_CONTENT: if (mps->content) return mps->content; if (mps->contentraw) { stream = mps->contentraw; camel_object_ref((CamelObject *)stream); } else if (mps->start1 == -1 && parse_content(mps) == -1 && (stream = ((CamelDataWrapper *)mps)->stream) == NULL) { g_warning("Trying to get content on an invalid multipart/signed"); return NULL; } else if (dw->stream == NULL) { return NULL; } else if (mps->start1 == -1) { stream = dw->stream; camel_object_ref(stream); } else { stream = camel_seekable_substream_new((CamelSeekableStream *)dw->stream, mps->start1, mps->end1); } camel_stream_reset(stream); mps->content = camel_mime_part_new(); camel_data_wrapper_construct_from_stream((CamelDataWrapper *)mps->content, stream); camel_object_unref(stream); return mps->content; case CAMEL_MULTIPART_SIGNED_SIGNATURE: if (mps->signature) return mps->signature; if (mps->start1 == -1 && parse_content(mps) == -1) { g_warning("Trying to get signature on invalid multipart/signed"); return NULL; } else if (dw->stream == NULL) { return NULL; } stream = camel_seekable_substream_new((CamelSeekableStream *)dw->stream, mps->start2, mps->end2); camel_stream_reset(stream); mps->signature = camel_mime_part_new(); camel_data_wrapper_construct_from_stream((CamelDataWrapper *)mps->signature, stream); camel_object_unref((CamelObject *)stream); return mps->signature; default: g_warning("trying to get object out of bounds for multipart"); } return NULL; } static guint signed_get_number(CamelMultipart *multipart) { CamelDataWrapper *dw = (CamelDataWrapper *)multipart; CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart; /* check what we have, so we return something reasonable */ if ((mps->content || mps->contentraw) && mps->signature) return 2; if (mps->start1 == -1 && parse_content(mps) == -1) { if (dw->stream == NULL) return 0; else return 1; } else { return 2; } } static void set_stream(CamelMultipartSigned *mps, CamelStream *mem) { CamelDataWrapper *dw = (CamelDataWrapper *)mps; if (dw->stream) camel_object_unref((CamelObject *)dw->stream); dw->stream = (CamelStream *)mem; mps->start1 = -1; if (mps->content) { camel_object_unref((CamelObject *)mps->content); mps->content = NULL; } if (mps->contentraw) { camel_object_unref((CamelObject *)mps->contentraw); mps->contentraw = NULL; } if (mps->signature) { camel_object_unref((CamelObject *)mps->signature); mps->signature = NULL; } } static int construct_from_stream(CamelDataWrapper *data_wrapper, CamelStream *stream) { CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper; CamelStream *mem = camel_stream_mem_new(); if (camel_stream_write_to_stream(stream, mem) == -1) return -1; set_stream(mps, mem); return 0; } static int signed_construct_from_parser(CamelMultipart *multipart, struct _CamelMimeParser *mp) { int err; struct _header_content_type *content_type; CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart; char *buf; size_t len; CamelStream *mem; /* we *must not* be in multipart state, otherwise the mime parser will parse the headers which is a no no @#$@# stupid multipart/signed spec */ g_assert(camel_mime_parser_state(mp) == HSCAN_HEADER); /* All we do is copy it to a memstream */ content_type = camel_mime_parser_content_type(mp); camel_multipart_set_boundary(multipart, header_content_type_param(content_type, "boundary")); mem = camel_stream_mem_new(); while (camel_mime_parser_step(mp, &buf, &len) != HSCAN_BODY_END) camel_stream_write(mem, buf, len); set_stream(mps, mem); err = camel_mime_parser_errno(mp); if (err != 0) { errno = err; return -1; } else return 0; } static int write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream) { CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper; CamelMultipart *mp = (CamelMultipart *)mps; const char *boundary; int count, total=0; /* we have 3 basic cases: 1. constructed, we write out the data wrapper stream we got 2. signed content, we create and write out a new stream 3. invalid */ /* 1 */ /* FIXME: locking? */ if (data_wrapper->stream) { camel_stream_reset(data_wrapper->stream); return camel_stream_write_to_stream(data_wrapper->stream, stream); } /* 3 */ if (mps->signature == NULL || mps->contentraw == NULL) return -1; /* 2 */ boundary = camel_multipart_get_boundary(mp); if (mp->preface) { count = camel_stream_write_string(stream, mp->preface); if (count == -1) return -1; total += count; } /* first boundary */ count = camel_stream_printf(stream, "\n--%s\n", boundary); if (count == -1) return -1; total += count; /* output content part */ camel_stream_reset(mps->contentraw); count = camel_stream_write_to_stream(mps->contentraw, stream); if (count == -1) return -1; total += count; /* boundary */ count = camel_stream_printf(stream, "\n--%s\n", boundary); if (count == -1) return -1; total += count; /* signature */ count = camel_data_wrapper_write_to_stream((CamelDataWrapper *)mps->signature, stream); if (count == -1) return -1; total += count; /* write the terminating boudary delimiter */ count = camel_stream_printf(stream, "\n--%s--\n", boundary); if (count == -1) return -1; total += count; /* and finally the postface */ if (mp->postface) { count = camel_stream_write_string(stream, mp->postface); if (count == -1) return -1; total += count; } return total; } /* See rfc3156, section 2 and others */ /* We do this simply: Anything not base64 must be qp This is so that we can safely translate any occurance of "From " into the quoted-printable escaped version safely. */ static void prepare_sign(CamelMimePart *mime_part) { CamelDataWrapper *wrapper; CamelMimePartEncodingType encoding; int parts, i; wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (!wrapper) return; if (CAMEL_IS_MULTIPART (wrapper)) { parts = camel_multipart_get_number((CamelMultipart *)wrapper); for (i = 0; i < parts; i++) prepare_sign(camel_multipart_get_part((CamelMultipart *)wrapper, i)); } else if (CAMEL_IS_MIME_MESSAGE (wrapper)) { prepare_sign((CamelMimePart *)wrapper); } else { encoding = camel_mime_part_get_encoding(mime_part); if (encoding != CAMEL_MIME_PART_ENCODING_BASE64 && encoding != CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE) { camel_mime_part_set_encoding(mime_part, CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE); } } } /** * camel_multipart_signed_sign: * @mps: * @context: The CipherContext to use for signing. * @content: CamelMimePart content you wish to sign/transport. * @userid: The id of the signing key to use. * @hash: The algorithm to use. * @ex: * * Sign the part @content, and attach it as the first part * (CAMEL_MULTIPART_SIGNED_CONTENT) of the multipart @mps. A * signature object will be created and setup as the second part * (CAMEL_MULTIPART_SIGNED_SIGNATURE) of the object. Once a part has * been successfully signed the mutlipart is ready for transmission. * * This method should be used to create multipart/signed objects * which are properly canoncalised before signing, etc. * * Return value: -1 on error, setting @ex appropriately. On error * neither the content or signature parts will be setup. **/ int camel_multipart_signed_sign(CamelMultipartSigned *mps, CamelCipherContext *context, CamelMimePart *content, const char *userid, CamelCipherHash hash, CamelException *ex) { CamelMimeFilter *canon_filter; CamelStream *sigstream, *mem; CamelStreamFilter *filter; CamelContentType *mime_type; CamelMimePart *signature; CamelDataWrapper *dw; char *type; /* this needs to be set */ g_return_val_if_fail(context->sign_protocol != NULL, -1); prepare_sign(content); mem = camel_stream_mem_new(); filter = camel_stream_filter_new_with_stream(mem); /* Note: see rfc2015 or rfc3156, section 5 */ canon_filter = camel_mime_filter_canon_new(CAMEL_MIME_FILTER_CANON_STRIP|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_FROM); camel_stream_filter_add(filter, (CamelMimeFilter *)canon_filter); camel_object_unref((CamelObject *)canon_filter); camel_data_wrapper_write_to_stream((CamelDataWrapper *)content, (CamelStream *)filter); camel_stream_flush((CamelStream *)filter); camel_object_unref((CamelObject *)filter); camel_stream_reset(mem); #if 0 printf("-- Signing:\n"); fwrite(((CamelStreamMem *)mem)->buffer->data, ((CamelStreamMem *)mem)->buffer->len, 1, stdout); printf("-- end\n"); #endif sigstream = camel_stream_mem_new(); if (camel_cipher_sign(context, userid, hash, mem, sigstream, ex) == -1) { camel_object_unref((CamelObject *)mem); camel_object_unref((CamelObject *)sigstream); return -1; } /* create the signature wrapper object */ signature = camel_mime_part_new(); dw = camel_data_wrapper_new(); type = alloca(strlen(context->sign_protocol) + 32); sprintf(type, "%s; name=signature.asc", context->sign_protocol); camel_data_wrapper_set_mime_type(dw, type); camel_stream_reset(sigstream); camel_data_wrapper_construct_from_stream(dw, sigstream); camel_object_unref((CamelObject *)sigstream); camel_medium_set_content_object((CamelMedium *)signature, dw); camel_object_unref((CamelObject *)dw); camel_mime_part_set_description(signature, _("This is a digitally signed message part")); /* setup our mime type and boundary */ mime_type = header_content_type_new("multipart", "signed"); header_content_type_set_param(mime_type, "micalg", camel_cipher_hash_to_id(context, hash)); header_content_type_set_param(mime_type, "protocol", context->sign_protocol); camel_data_wrapper_set_mime_type_field(CAMEL_DATA_WRAPPER (mps), mime_type); header_content_type_unref(mime_type); camel_multipart_set_boundary((CamelMultipart *)mps, NULL); /* just keep the whole raw content. We dont *really* need to do this because we know how we just proccessed it, but, well, better to be safe than sorry */ mps->signature = signature; mps->contentraw = mem; camel_stream_reset(mem); /* clear the data-wrapper stream - tells write_to_stream to use the right object */ if (((CamelDataWrapper *)mps)->stream) { camel_object_unref((CamelObject *) ((CamelDataWrapper *)mps)->stream); ((CamelDataWrapper *)mps)->stream = NULL; } return 0; } /** * camel_multipart_signed_verify: * @mps: * @context: * @ex: * * Verify a signed object. This may be used to verify newly signed * objects as well as those created from external streams or parsers. * * Return value: A validity value, or NULL on error, setting @ex * appropriately. **/ CamelCipherValidity * camel_multipart_signed_verify(CamelMultipartSigned *mps, CamelCipherContext *context, CamelException *ex) { CamelCipherValidity *valid; CamelMimePart *sigpart; CamelStream *sigstream, *constream; /* we need to be able to verify stuff we just signed as well as stuff we loaded from a stream/parser */ if (mps->contentraw) { constream = mps->contentraw; camel_object_ref((CamelObject *)constream); } else { CamelStream *sub; CamelMimeFilter *canon_filter; if (mps->start1 == -1 && parse_content(mps) == -1) { camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("parse error")); return NULL; } /* first, prepare our parts */ sub = camel_seekable_substream_new((CamelSeekableStream *)((CamelDataWrapper *)mps)->stream, mps->start1, mps->end1); constream = (CamelStream *)camel_stream_filter_new_with_stream(sub); camel_object_unref((CamelObject *)sub); /* Note: see rfc2015 or rfc3156, section 5 */ canon_filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF); camel_stream_filter_add((CamelStreamFilter *)constream, (CamelMimeFilter *)canon_filter); camel_object_unref((CamelObject *)canon_filter); } /* we do this as a normal mime part so we can have it handle transfer encoding etc */ sigstream = camel_stream_mem_new(); sigpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_SIGNATURE); camel_data_wrapper_write_to_stream((CamelDataWrapper *)sigpart, sigstream); camel_stream_reset(sigstream); /* do the magic, the caller must supply the right context for this kind of object */ valid = camel_cipher_verify(context, camel_cipher_id_to_hash(context, mps->micalg), constream, sigstream, ex); #if 0 { CamelStream *sout = camel_stream_fs_new_with_fd(dup(0)); camel_stream_printf(sout, "-- Verifying:\n"); camel_stream_reset(constream); camel_stream_write_to_stream(constream, sout); camel_stream_printf(sout, "-- end\n"); camel_object_unref((CamelObject *)sout); } #endif camel_object_unref((CamelObject *)constream); camel_object_unref((CamelObject *)sigstream); return valid; }