-
-
Save skx/ba07ba7fbb0788c6ba68 to your computer and use it in GitHub Desktop.
Simple utility script to dump attachments, via GMIME
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* AD.c - Attachment dumper. | |
*** | |
* | |
* This is a simple script which is designed to write out the attachments | |
* from an email to disk. | |
* | |
* Compile like so: | |
* | |
* $ gcc -Wall -O2 ad.c -o ad $(pkg-config --libs --cflags gmime-2.6) | |
* | |
* Specify the path to a (Maildir) message on the command-line and attachments | |
* will be written to the current working directory. | |
* | |
* For example: | |
* | |
* $ ./ad ~/Mail/.razor/cur/1410371138.30015_2.ssh.steve.org.uk\:2\,S --mime image/jpeg | |
* | |
* Here we've used "--mime" to specify that only attachments with a | |
* Content-Type: image/jpeg-header will be dumped. | |
* | |
* You may also invoke the script with `--dump` to just show the attachments: | |
* | |
* Parsing: ./1410371138.30015_2.ssh.steve.org.uk:2,S | |
* Attachment name: picture1.jpg | |
* MIME-Type: application/octet-stream | |
* Attachment name: picture2.jpg | |
* MIME-Type: application/octet-stream | |
* Attachment name: picture3.jpg | |
* MIME-Type: application/octet-stream | |
* Attachment name: picture4.jpg | |
* MIME-Type: application/octet-stream | |
* Attachment name: picture5.jpg | |
* MIME-Type: application/octet-stream | |
* There were 6 attachments | |
* | |
* NOTE: --dump will stop the writing from happening. | |
* | |
* | |
* Options | |
* ------- | |
* | |
* --dump - Only show attachments | |
* --verbose - Some more details. | |
* --name str - Write attachments whos filename matches the string. | |
* --output /dir - Write attachments to given directory. | |
* --mime type - Only write out matching parts. | |
* | |
* | |
* CAVEATS | |
* ------- | |
* | |
* We don't filter attachment names. If you have an attachment with an | |
* absolute-name, such as "/etc/passwd" bad things will happen, if you're | |
* crazy enough to run this as root. | |
* | |
* | |
* SCRIPTING | |
* --------- | |
* | |
* $ find ~/Maildir/.people.kirsi/ -type f -exec $PWD/ad --mime=application/pdf \{\} \; | |
* | |
* Fun. | |
* | |
* | |
* Steve | |
* -- | |
* | |
*/ | |
#include <glib.h> | |
#include <gmime/gmime.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <getopt.h> | |
/** | |
* Global options. | |
*/ | |
static int g_debug = 0; | |
static int g_verbose = 0; | |
/* Match on MIME-type. */ | |
static char *g_mime = NULL; | |
/* Match on attachment filename. */ | |
static char *g_file_name = NULL; | |
/* where to write, defaults to "./" */ | |
static char *g_output_directory = NULL; | |
/** | |
* Parse the given message. | |
*/ | |
static GMimeMessage *parse_message(int fd) | |
{ | |
GMimeMessage *message; | |
GMimeParser *parser; | |
GMimeStream *stream; | |
stream = g_mime_stream_fs_new(fd); | |
parser = g_mime_parser_new_with_stream(stream); | |
/* | |
* unref the stream (parser owns a ref, so this object does not | |
* actually get free'd until we destroy the parser) | |
**/ | |
g_object_unref(stream); | |
message = g_mime_parser_construct_message(parser); | |
if (!message) | |
return NULL; | |
g_object_unref(parser); | |
return message; | |
} | |
/** | |
* This function is invoked for each "part" of the message. | |
* | |
* The part might be an attachment, another message, or something else .. | |
*/ | |
static void message_part_callback(GMimeObject * parent, GMimeObject * part, gpointer user_data) | |
{ | |
int *count = (int *) user_data; | |
if (GMIME_IS_MESSAGE_PART(part)) | |
{ | |
/* message/rfc822 or message/news */ | |
GMimeMessage *message; | |
/* Recurse */ | |
message = g_mime_message_part_get_message((GMimeMessagePart *) part); | |
g_mime_message_foreach(message, message_part_callback, &count); | |
} else if (GMIME_IS_MESSAGE_PARTIAL(part)) | |
{ | |
/* message/partial */ | |
} else if (GMIME_IS_MULTIPART(part)) | |
{ | |
/* multipart/mixed, multipart/alternative, | |
* multipart/related, multipart/signed, | |
* multipart/encrypted, etc... */ | |
} else if (GMIME_IS_PART(part)) | |
{ | |
(*count)++; | |
GMimeContentType *ct = g_mime_object_get_content_type(part); | |
/* a normal leaf part, could be text/plain or image/jpeg etc */ | |
GMimeContentDisposition *disp = NULL; | |
disp = g_mime_object_get_content_disposition(part); | |
if ((disp != NULL) && | |
(!g_ascii_strcasecmp(disp->disposition, "attachment"))) | |
{ | |
char *aname = (char *) g_mime_object_get_content_disposition_parameter(part, | |
"filename"); | |
if (aname == NULL) | |
aname = (char *) g_mime_object_get_content_type_parameter(part, "name"); | |
if (aname == NULL) | |
return; | |
/** | |
* Skip any MIME types that don't match. | |
*/ | |
if (g_mime) | |
{ | |
char buf[128] = { '\0' }; | |
snprintf(buf, sizeof(buf) - 1, "%s/%s", ct->type, ct->subtype); | |
if (strcasestr(buf, g_mime) == NULL) | |
{ | |
if (g_verbose) | |
{ | |
printf("Skipping attachment of type '%s'\n", buf); | |
} | |
return; | |
} | |
} | |
/** | |
* Skip any filenames that don't match. | |
*/ | |
if (g_file_name) | |
{ | |
if (strcasestr(aname, g_file_name) == NULL) | |
{ | |
if (g_verbose) | |
{ | |
printf("Skipping attachment name which doesn't match '%s'\n", aname); | |
} | |
return; | |
} | |
} | |
/** | |
* If dumping then just show the data and return. | |
*/ | |
if (g_debug) | |
{ | |
printf("\tAttachment name: %s\n", aname); | |
printf("\tMIME-Type: %s/%s\n", ct->type, ct->subtype); | |
return; | |
} | |
/** | |
* Get the attachment data. | |
*/ | |
GMimeStream *mem = g_mime_stream_mem_new(); | |
if (GMIME_IS_MESSAGE_PART(part)) | |
{ | |
GMimeMessage *msg = g_mime_message_part_get_message(GMIME_MESSAGE_PART(part)); | |
g_mime_object_write_to_stream(GMIME_OBJECT(msg), mem); | |
} else | |
{ | |
GMimeDataWrapper *content = g_mime_part_get_content_object(GMIME_PART(part)); | |
g_mime_data_wrapper_write_to_stream(content, mem); | |
} | |
/** | |
* NOTE: by setting the owner to FALSE, it means unreffing the | |
* memory stream won't free the GByteArray data. | |
*/ | |
g_mime_stream_mem_set_owner(GMIME_STREAM_MEM(mem), FALSE); | |
GByteArray *res = g_mime_stream_mem_get_byte_array(GMIME_STREAM_MEM(mem)); | |
/** | |
* The actual data from the array, and the size of that data. | |
*/ | |
char *adata = (char *) res->data; | |
size_t len = (res->len); | |
if ((adata != NULL) && (len > 0)) | |
{ | |
/** | |
* Build up output directory, as specified via --output | |
*/ | |
char out[1024] = { '\0' }; | |
if (g_output_directory) | |
snprintf(out, sizeof(out) - 1, "%s/%s", g_output_directory, aname); | |
else | |
snprintf(out, sizeof(out) - 1, "%s", aname); | |
FILE *fout = fopen(out, "wbx"); | |
if (fout) | |
{ | |
fwrite(adata, len, 1, fout); | |
fclose(fout); | |
} else | |
{ | |
if (errno == EEXIST) | |
{ | |
printf("Refused to overwite existing file: %s\n", out); | |
return; | |
} | |
} | |
if (g_verbose) | |
printf("Wrote %d bytes to %s\n", (int) len, aname); | |
} | |
g_object_unref(mem); | |
} | |
} else | |
{ | |
/** Unknown part-type. */ | |
g_assert_not_reached(); | |
} | |
} | |
/** | |
* Entry point. | |
*/ | |
int main(int argc, char **argv) | |
{ | |
/** | |
* Parse our options. | |
*/ | |
while (1) | |
{ | |
static struct option long_options[] = { | |
{"dump", no_argument, 0, 'd'}, | |
{"output", required_argument, 0, 'o'}, | |
{"name", required_argument, 0, 'n'}, | |
{"mime", required_argument, 0, 'm'}, | |
{"verbose", no_argument, 0, 'v'}, | |
{0, 0, 0, 0} | |
}; | |
char c; | |
/* getopt_long stores the option index here. */ | |
int option_index = 0; | |
c = getopt_long(argc, argv, "vdm:n:o:", long_options, &option_index); | |
/* Detect the end of the options. */ | |
if (c == -1) | |
break; | |
switch (c) | |
{ | |
case 'd': | |
g_debug = 1; | |
break; | |
case 'v': | |
g_verbose = 1; | |
break; | |
case 'm': | |
g_mime = strdup(optarg); | |
break; | |
case 'n': | |
g_file_name = strdup(optarg); | |
break; | |
case 'o': | |
g_output_directory = strdup(optarg); | |
break; | |
case '?': | |
/* getopt_long already printed an error message. */ | |
return (1); | |
} | |
} | |
if (optind < 1) | |
{ | |
fprintf(stderr, "Cannot read from stdin\n"); | |
return -1; | |
} | |
/* init the gmime library */ | |
g_mime_init(0); | |
/** | |
* If a configuration file was specified use a different one. | |
*/ | |
int index; | |
for (index = optind; index < argc; index++) | |
{ | |
/* parse the message */ | |
int fd; | |
if ((fd = open(argv[index], O_RDONLY, 0)) == -1) | |
{ | |
fprintf(stderr, "Cannot open message `%s': %s\n", argv[1], g_strerror(errno)); | |
return 0; | |
} | |
if (g_debug) | |
printf("Parsing: %s\n", argv[index]); | |
GMimeMessage *message = parse_message(fd); | |
if (message) | |
{ | |
/* Invoke our callback */ | |
int count = 0; | |
g_mime_message_foreach(message, message_part_callback, &count); | |
if (g_debug) | |
printf("\tThere were %d attachments\n\n", count); | |
/* free the mesage */ | |
g_object_unref(message); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was linked to from my blog: