Browse Source

Merge branch 'develop'

tags/fmail-0.2.0^0
parent
commit
afa7140f78
8 changed files with 250 additions and 133 deletions
  1. +6
    -0
      NEWS
  2. +5
    -4
      README.md
  3. +1
    -1
      configure.ac
  4. +0
    -2
      lib/Makefile.am
  5. +62
    -88
      lib/sbuffer.c
  6. +12
    -11
      lib/sbuffer.h
  7. +9
    -2
      man/fmail.1.in
  8. +155
    -25
      src/fmail.c

+ 6
- 0
NEWS View File

@@ -1,3 +1,9 @@
Changes in fmail 0.2.0 (2019-01-12)

* Prune path components in an attachment’s filename.
* Support encryption with the -E, --encrypt option.


Changes in fmail 0.1.2 (2018-02-26)

* Correctly accept -U and -D options.


+ 5
- 4
README.md View File

@@ -9,9 +9,9 @@ ready to be sent to a mail submission agent.

The name “fmail” stands for something like “Formatting MAIL”.

The main feature of Fmail is its ability to add attachments to the
input message and to sign the whole message (including attachments)
using [PGP/MIME](https://tools.ietf.org/html/rfc3156).
The main feature of Fmail is its ability to add attachments to the input
message and to sign and/or encrypt the whole message (including
attachments) using [PGP/MIME](https://tools.ietf.org/html/rfc3156).

The signing feature makes Fmail usable as a “signing filter”,
taking a complete (but unsigned) mail on its standard input and
@@ -23,7 +23,8 @@ Install
-------
Fmail depends on the following libraries:

- [libgpgme](http://www.gnupg.org/gpgme.html), to sign messages;
- [libgpgme](http://www.gnupg.org/gpgme.html), to sign and encrypt
messages.

- [libmagic](http://www.darwinsys.com/file/), to guess file type and
encoding of attachments.


+ 1
- 1
configure.ac View File

@@ -1,6 +1,6 @@
dnl Configure template for the fmail package

AC_INIT([fmail], [0.1.2],
AC_INIT([fmail], [0.2.0],
[dgouttegattat@incenp.org])
AC_CONFIG_SRCDIR([configure.ac])
AC_CONFIG_MACRO_DIR([m4])


+ 0
- 2
lib/Makefile.am View File

@@ -4,5 +4,3 @@ libfmail_a_SOURCES = err.compat.h compat.h xmem.c xmem.h \
sbuffer.c sbuffer.h

libfmail_a_LIBADD = $(LIBOBJS)

DEFS = -DEXIT_ON_ENOMEM @DEFS@

+ 62
- 88
lib/sbuffer.c View File

@@ -1,6 +1,6 @@
/*
* sbuffer - Incenp.org Notch Library: string buffer module
* Copyright (C) 2011 Damien Goutte-Gattat
* Copyright (C) 2011,2018 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,84 +25,52 @@
#include <errno.h>
#include <string.h>

#ifdef EXIT_ON_ENOMEM
#include <xmem.h>
#endif

struct string_buffer_t
{
char *buffer;
size_t len;
size_t size;
size_t block;
};

#define AVAILABLE_SIZE(s) ((s)->size - (s)->len)
#define CURSOR(s) ((s)->buffer + (s)->len)

#ifdef EXIT_ON_NOMEM

#define GROW(s,l) \
do { \
(s)->size = ((((s)->len + (l)) / (s)->block) + 1) * (s)->block; \
(s)->buffer = xrealloc((s)->buffer, (s)->size); \
} while ( 0 )

#else

#define GROW(s,l) \
do { \
size_t ns = ((((s)->len + (l)) / (s)->block) + 1) * (s)->block; \
char *np = realloc((s)->buffer, ns); \
char *np = xrealloc((s)->buffer, ns); \
if ( ! np ) \
return -1; \
(s)->buffer = np; \
(s)->size = ns; \
(s)->error = errno; \
else { \
(s)->buffer = np; \
(s)->size = ns; \
} \
} while ( 0 )

#endif

string_buffer_t *
sb_new(size_t block_size)
int
sb_init(string_buffer_t *s, size_t block_size)
{
string_buffer_t *s;

if ( ! block_size )
block_size = 128;

#ifdef EXIT_ON_ENOMEM
s = xmalloc(sizeof(*s));
s->block = s->size = block_size;
s->buffer = xmalloc(block_size);
sb_empty(s);
#else
if ( (s = malloc(sizeof(*s))) ) {
s->block = s->size = block_size;
if ( (s->buffer = malloc(block_size)) )
sb_empty(s);
else {
free(s);
s = NULL;
}
if ( ! s ) {
errno = EINVAL;
return -1;
}
#endif

return s;
}
s->buffer = NULL;
s->len = s->size = 0;
s->block = block_size ? block_size : 128;
s->error = 0;

void
sb_free(string_buffer_t *s)
{
if ( s ) {
free(s->buffer);
free(s);
}
return 0;
}

const char *
sb_get(string_buffer_t *s)
{
return s ? s->buffer : NULL;
const char *str = NULL;

if ( ! s )
errno = EINVAL;
else if ( s->error )
errno = s->error;
else
str = s->buffer;

return str;
}

char *
@@ -110,25 +78,16 @@ sb_get_copy(string_buffer_t *s)
{
char *str = NULL;

if ( s ) {
#ifdef EXIT_ON_ENOMEM
str = xmalloc(s->len + 1);
#else
if ( ! (str = malloc(s->len + 1)) )
return NULL;
#endif
strncpy(str, s->buffer, s->len);
}
if ( ! s )
errno = EINVAL;
else if ( s->error )
errno = s->error;
else if ( (str = xmalloc(s->len + 1)) )
strncpy(str, s->buffer, s->len + 1);

return str;
}

size_t
sb_len(string_buffer_t *s)
{
return s ? s->len : 0;
}

int
sb_empty(string_buffer_t *s)
{
@@ -137,8 +96,9 @@ sb_empty(string_buffer_t *s)
return -1;
}

s->buffer[0] = '\0';
s->len = 0;
if ( s->buffer )
s->buffer[0] = '\0';

return 0;
}
@@ -151,11 +111,13 @@ sb_addc(string_buffer_t *s, char c)
return -1;
}

if ( AVAILABLE_SIZE(s) < 1 )
GROW(s, 1);
if ( AVAILABLE_SIZE(s) < 2 )
GROW(s, 2);

*(s->buffer + s->len++) = c;
*(s->buffer + s->len) = '\0';
if ( ! s->error ) {
*(s->buffer + s->len++) = c;
*(s->buffer + s->len) = '\0';
}

return 0;
}
@@ -179,11 +141,13 @@ sb_addn(string_buffer_t *s, const char *buffer, size_t len)
return -1;
}

if ( AVAILABLE_SIZE(s) < len )
GROW(s, len);
if ( AVAILABLE_SIZE(s) < len + 1)
GROW(s, len + 1);

strncat(CURSOR(s), buffer, len);
s->len += len;
if ( ! s->error ) {
strncat(CURSOR(s), buffer, len);
s->len += len;
}

return 0;
}
@@ -199,11 +163,19 @@ sb_vaddf(string_buffer_t *s, const char *fmt, va_list ap)
return -1;
}

if ( ! s->size )
GROW(s, s->block);

if ( s->error )
return 0;

va_copy(aq, ap);
written = vsnprintf(CURSOR(s), AVAILABLE_SIZE(s), fmt, aq);
va_end(aq);
if ( written >= AVAILABLE_SIZE(s) ) {
if ( written >= (int)AVAILABLE_SIZE(s) ) {
GROW(s, written);
if ( s->error )
return 0;
vsprintf(CURSOR(s), fmt, ap);
}
s->len += written;
@@ -214,7 +186,7 @@ sb_vaddf(string_buffer_t *s, const char *fmt, va_list ap)
int
sb_addf(string_buffer_t *s, const char *fmt, ...)
{
int ret;
int ret = 0;
va_list ap;

if ( ! s || ! fmt ) {
@@ -222,9 +194,11 @@ sb_addf(string_buffer_t *s, const char *fmt, ...)
return -1;
}

va_start(ap, fmt);
ret = sb_vaddf(s, fmt, ap);
va_end(ap);
if ( ! s->error ) {
va_start(ap, fmt);
ret = sb_vaddf(s, fmt, ap);
va_end(ap);
}

return ret;
}

+ 12
- 11
lib/sbuffer.h View File

@@ -1,6 +1,6 @@
/*
* sbuffer - Incenp.org Notch Library: string buffer module
* Copyright (C) 2011 Damien Goutte-Gattat
* Copyright (C) 2011,2018 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,17 +22,21 @@
#include <stdlib.h>
#include <stdarg.h>

typedef struct
{
char *buffer;
size_t len;
size_t size;
size_t block;
int error;
} string_buffer_t;

#ifdef __cpluscplus
extern "C" {
#endif

typedef struct string_buffer_t string_buffer_t;

string_buffer_t *
sb_new(size_t);

void
sb_free(string_buffer_t *);
int
sb_init(string_buffer_t *, size_t);

const char *
sb_get(string_buffer_t *);
@@ -40,9 +44,6 @@ sb_get(string_buffer_t *);
char *
sb_get_copy(string_buffer_t *);

size_t
sb_len(string_buffer_t *);

int
sb_empty(string_buffer_t *);



+ 9
- 2
man/fmail.1.in View File

@@ -10,6 +10,8 @@ fmail \- mail formatter
.RB [ \-a | --attach
.IR file ]
.RB [ \-s | --sign ]
.RB [ \-E | --encrypt
.IR recipient ]
.RB [ \-e | --edit ]
.RB [ \-f | --footer
.IR file ]
@@ -40,7 +42,8 @@ OpenPGP signature; encoding attachments (in
.I Base64
or
.I Quoted-Printable
depending on their type); adding appropriate mail headers.
depending on their type); adding appropriate mail headers;
and encrypting to one or several OpenPGP public keys.
.PP
The resulting message is printed on standard output and can
be piped directly to a program such as
@@ -66,6 +69,10 @@ Attach the specified
.BR -s ", " --sign
Sign the message.
.TP
.BR -E ", " --encrypt " " \fIrecipient\fR
Encrypt the message to the public key of
the specified recipient.
.TP
.BR -e ", " --edit
Fire an editor to type the mail body instead of
reading it from standard input.
@@ -118,7 +125,7 @@ Damien Goutte-Gattat
.SH COPYRIGHT
.ad l
.PP
Copyright \(co 2011,2018 Damien Goutte-Gattat
Copyright \(co 2011,2018,2019 Damien Goutte-Gattat
.PP
This program is released under the GNU General Public License.
See the COPYING file in the source distribution or


+ 155
- 25
src/fmail.c View File

@@ -1,6 +1,6 @@
/*
* fmail - Mail formatter
* Copyright (C) 2011,2018 Damien Goutte-Gattat
* Copyright (C) 2011,2018,2019 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -80,6 +80,7 @@ for submission to a mail submission agent.\n");
puts("\
Cryptography options:\n\
-s, --sign Sign the message.\n\
-E, --encrypt RECP Encrypt for the specified recipient.\n\
");

printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
@@ -92,7 +93,7 @@ info(void)
{
printf("\
fmail %s\n\
Copyright (C) 2018 Damien Goutte-Gattat\n\
Copyright (C) 2019 Damien Goutte-Gattat\n\
\n\
This program is released under the GNU General Public License.\n\
See the COPYING file or <http://www.gnu.org/licenses/gpl.html>.\n\
@@ -113,9 +114,11 @@ typedef struct fmail_ctx
{
const char **attachments;
size_t att_count;
const char **recipients;
size_t rcp_count;
const char *footer;
magic_t magic_ctx;
gpgme_ctx_t signing_ctx;
gpgme_ctx_t crypto_ctx;
FILE *input;
} fmail_ctx_t;

@@ -299,7 +302,7 @@ process_text_body(fmail_ctx_t *ctx, FILE *out)


/* Signing stuff. */
/* Crypto stuff. */

gpgme_ctx_t
initialize_gpgme(void)
@@ -329,7 +332,7 @@ hash_algo_to_string(gpgme_hash_algo_t algo)
case GPGME_MD_RMD160: return "pgp-ripemd160";
case GPGME_MD_MD2: return "pgp-md2";
case GPGME_MD_TIGER: return "pgp-tiger192";
case GPGME_MD_HAVAL: return "php-haval-5-160";
case GPGME_MD_HAVAL: return "pgp-haval-5-160";
case GPGME_MD_SHA256: return "pgp-sha256";
case GPGME_MD_SHA384: return "pgp-sha384";
case GPGME_MD_SHA512: return "pgp-sha512";
@@ -401,6 +404,107 @@ sign_stream(gpgme_ctx_t ctx, FILE *in, FILE *out)
gpgme_data_release(gout);
}

static gpgme_key_t *
get_recipient_keys(gpgme_ctx_t ctx, const char **recipients, size_t nr)
{
gpgme_key_t *keys, key;
gpgme_error_t gerr;
unsigned i, j, k;

keys = NULL;

for ( i = j = gerr = 0; i < nr && ! gerr ; i++ ) {
k = j;
gerr = gpgme_op_keylist_start (ctx, recipients[i], 0);
while ( ! gerr ) {
gerr = gpgme_op_keylist_next (ctx, &key);
if ( gerr )
break;

if ( j % 10 == 0 )
keys = xrealloc(keys, j + 10);
keys[j++] = key;
}
if ( gpgme_err_code(gerr) == GPG_ERR_EOF )
gerr = 0;

if ( k == j )
errx(EXIT_FAILURE, "no key found for recipient %s", recipients[i]);
}
if ( gerr != 0 && gpgme_err_code(gerr) != GPG_ERR_EOF)
errx(EXIT_FAILURE, "cannot get recipient keys: %s", gpgme_strerror(gerr));

/* GpgME expects a NULL-terminated array of keys. */
if ( j % 10 == 0 )
keys = xrealloc(keys, j + 1);
keys[j++] = NULL;

return keys;
}

static void
encrypt_stream(gpgme_ctx_t ctx,
FILE *in,
FILE *out,
const char **recipients,
size_t nr,
int sign)
{
gpgme_data_t gin, gout;
gpgme_key_t *keys, *key;
gpgme_error_t gerr;
char boundary[32], buffer[512];
int n;

keys = get_recipient_keys(ctx, recipients, nr);
gpgme_data_new_from_stream(&gin, in);
gpgme_data_new(&gout);

if ( sign )
gerr = gpgme_op_encrypt_sign(ctx, keys, 0, gin, gout);
else
gerr = gpgme_op_encrypt(ctx, keys, 0, gin, gout);
if ( gerr != GPG_ERR_NO_ERROR )
errx(EXIT_FAILURE, "encrypting failed: %s", gpgme_strerror(gerr));

generate_boundary(boundary, sizeof(boundary));
fprintf(out, "Content-Type: multipart/encrypted;\r\n"
" boundary=\"%s\";\r\n"
" protocol=\"application/pgp-encrypted\r\n"
"\r\n"
"--%s\r\n"
"Content-Type: application/pgp-encrypted\r\n"
"\r\n"
"Version: 1\r\n"
"\r\n"
"--%s\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n",
boundary, boundary, boundary);

gpgme_data_seek(gout, 0, SEEK_SET);
while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
int i = 0;

while ( i < n ) {
if ( buffer[i] == '\n' )
fputc('\r', out);
fputc(buffer[i], out);

i += 1;
}
}

fprintf(out, "\r\n--%s--\r\n", boundary);

gpgme_data_release(gin);
gpgme_data_release(gout);

for ( key = keys; *key != NULL; key++ )
gpgme_key_release(*key);
free(keys);
}


/* Attachments stuff. */
@@ -419,6 +523,13 @@ initialize_magic(void)
return ctx;
}

static const char *
get_basename(const char *filename)
{
char *last_slash = strrchr(filename, '/');
return last_slash ? last_slash + 1 : filename;
}

static void
process_attachment(const char *filename, magic_t ctx, FILE *out)
{
@@ -439,7 +550,7 @@ process_attachment(const char *filename, magic_t ctx, FILE *out)

fprintf(out, "Content-Type: %s\r\n", mime);
fprintf(out, "Content-Transfer-Encoding: %s\r\n", binary ? "base64" : "quoted-printable");
fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", filename);
fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", get_basename(filename));

f = fopen(filename, "r");
if ( binary )
@@ -525,13 +636,15 @@ int
main(int argc, char *argv[])
{
char c, with_useragent, with_date;
string_buffer_t *headers;
string_buffer_t headers;
fmail_ctx_t ctx;
int do_sign;

struct option options[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'v' },
{ "sign", 0, NULL, 's' },
{ "encrypt", 1, NULL, 'E' },
{ "attach", 1, NULL, 'a' },
{ "edit", 0, NULL, 'e' },
{ "footer", 1, NULL, 'f' },
@@ -549,17 +662,19 @@ main(int argc, char *argv[])
setlocale(LC_ALL, "");
srand(time(NULL));

with_useragent = with_date = 0;
with_useragent = with_date = do_sign = 0;

headers = sb_new(0);
ctx.signing_ctx = NULL;
sb_init(&headers, 0);
ctx.crypto_ctx = NULL;
ctx.magic_ctx = initialize_magic();
ctx.attachments = NULL;
ctx.att_count = 0;
ctx.recipients = NULL;
ctx.rcp_count = 0;
ctx.footer = NULL;
ctx.input = stdin;

while ( (c = getopt_long(argc, argv, "hvsa:ef:H:F:T:C:S:UD",
while ( (c = getopt_long(argc, argv, "hvsE:a:ef:H:F:T:C:S:UD",
options, NULL)) != -1 ) {
switch ( c ) {
case 'h':
@@ -575,8 +690,13 @@ main(int argc, char *argv[])
break;

case 's':
if ( ! ctx.signing_ctx )
ctx.signing_ctx = initialize_gpgme();
do_sign = 1;
break;

case 'E':
if ( ctx.rcp_count % 10 == 0 )
ctx.recipients = xrealloc(ctx.recipients, ctx.rcp_count + 10);
ctx.recipients[ctx.rcp_count++] = optarg;
break;

case 'a':
@@ -594,23 +714,23 @@ main(int argc, char *argv[])
break;

case 'H':
sb_addf(headers, "%s\r\n", optarg);
sb_addf(&headers, "%s\r\n", optarg);
break;

case 'F':
sb_addf(headers, "From: %s\r\n", optarg);
sb_addf(&headers, "From: %s\r\n", optarg);
break;

case 'T':
sb_addf(headers, "To: %s\r\n", optarg);
sb_addf(&headers, "To: %s\r\n", optarg);
break;

case 'C':
sb_addf(headers, "Cc: %s\r\n", optarg);
sb_addf(&headers, "Cc: %s\r\n", optarg);
break;

case 'S':
sb_addf(headers, "Subject: %s\r\n", optarg);
sb_addf(&headers, "Subject: %s\r\n", optarg);
break;

case 'U':
@@ -623,31 +743,38 @@ main(int argc, char *argv[])
}
}

if ( do_sign || ctx.rcp_count )
ctx.crypto_ctx = initialize_gpgme();

/* Generate automatic headers */
if ( with_date )
sb_addf(headers, "Date: %s\r\n", rfc2822_date());
sb_addf(&headers, "Date: %s\r\n", rfc2822_date());
if ( with_useragent )
sb_addf(headers, "User-Agent: fmail %s\r\n", VERSION);
sb_addf(&headers, "User-Agent: fmail %s\r\n", VERSION);

/* Fire editor instead of reading from stdin? */
if ( ! ctx.input )
ctx.input = read_input_from_editor();

/* Read user-provided headers. */
read_headers(ctx.input, headers);
read_headers(ctx.input, &headers);

/* Write all headers. */
fprintf(stdout, "%s", sb_get(headers));
fprintf(stdout, "%s", headers.buffer);
free(headers.buffer);

if ( ctx.signing_ctx ) {
if ( do_sign || ctx.rcp_count ) {
FILE *tmp = tmpfile();

process_text_body(&ctx, tmp);
fseek(tmp, 0, SEEK_SET);
sign_stream(ctx.signing_ctx, tmp, stdout);
if ( ctx.rcp_count == 0 )
sign_stream(ctx.crypto_ctx, tmp, stdout);
else
encrypt_stream(ctx.crypto_ctx, tmp, stdout, ctx.recipients, ctx.rcp_count, do_sign);

fclose(tmp);
gpgme_release(ctx.signing_ctx);
gpgme_release(ctx.crypto_ctx);
}
else
process_text_body(&ctx, stdout);
@@ -657,6 +784,9 @@ main(int argc, char *argv[])
if ( ctx.attachments )
free(ctx.attachments);

if ( ctx.recipients )
free(ctx.recipients);

magic_close(ctx.magic_ctx);

return EXIT_SUCCESS;


Loading…
Cancel
Save