From d6e72249142d3f74b18d05100aaf5cb92919b4ef Mon Sep 17 00:00:00 2001 From: Heri Sim Date: Mon, 25 Jul 2016 23:00:38 +0700 Subject: [PATCH 1/3] Use String.to_existing_atom instead of unsafe operation Should never allow external input from creating atoms in Erlang. Because, it allows Denial Of Service using mime headers, causing the VM to crash. --- lib/mimemail.ex | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/mimemail.ex b/lib/mimemail.ex index 5e90c9e..fa42d7b 100644 --- a/lib/mimemail.ex +++ b/lib/mimemail.ex @@ -1,4 +1,7 @@ defmodule MimeMail do + @on_load :preload_atoms + @atoms_to_preload [:'content-transfer-encoding', :'content-type', :'content-disposition', :'content-id', :'delivered-to'] + @type header :: {:raw,binary} | MimeMail.Header.t #ever the raw line or any term implementing MimeMail.Header.to_ascii @type body :: binary | [MimeMail.t] | {:raw,binary} #ever the raw body or list of mail for multipart or binary for decoded content @type t :: %MimeMail{headers: [{key::binary,header}], body: body} @@ -10,6 +13,8 @@ defmodule MimeMail do defdelegate get(dict,k,v), to: Map defdelegate pop(dict,k), to: Map + def preload_atoms, do: @atoms_to_preload |> Enum.each(&is_atom/1) + def from_string(data) do [headers, body] = case String.split(data, "\r\n\r\n", parts: 2) do two_parts = [_, _] -> two_parts @@ -19,7 +24,14 @@ defmodule MimeMail do |> String.replace(~r/\r\n([^\t ])/,"\r\n!\\1") |> String.split("\r\n!") |> Enum.map(&{String.split(&1,~r/\s*:/,parts: 2),&1}) - headers=for {[k,_],v}<-headers, do: {:"#{String.downcase(k)}", {:raw,v}} + headers = for {[k,_],v}<-headers do + k = k |> String.downcase + try do + {k |> String.to_existing_atom, {:raw,v}} + rescue + ArgumentError -> {k, {:raw,v}} + end + end %MimeMail{headers: headers, body: {:raw,body}} end From eb38e0754a641ac372e7245c67420236f84615fb Mon Sep 17 00:00:00 2001 From: Heri Sim Date: Mon, 25 Jul 2016 23:03:00 +0700 Subject: [PATCH 2/3] Fix Unsafe operation: Use String.to_existing_atom to parse headers Should never allow external input from creating atoms in Erlang. Because, it allows Denial Of Service using mime headers, causing the VM to crash. --- lib/mimemail_headers.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/mimemail_headers.ex b/lib/mimemail_headers.ex index 9fc809a..3cd1b1e 100644 --- a/lib/mimemail_headers.ex +++ b/lib/mimemail_headers.ex @@ -23,7 +23,7 @@ defmodule MimeMail.Emails do parsed=for {k,{:raw,v}}<-headers, k in [:from,:to,:cc,:cci,:'delivered-to'] do {k,v|>MimeMail.header_value|>parse_header} end - %{mail| headers: Enum.reduce(parsed,headers, fn {k,v},acc-> Dict.put(acc,k,v) end)} + %{mail| headers: Enum.reduce(parsed,headers, fn {k,v},acc-> List.keystore(acc,k,0,{k,v}) end)} end defimpl MimeMail.Header, for: List do # a list header is a mailbox spec list def to_ascii(mail_list) do # a mail is a struct %{name: nil, address: ""} @@ -40,9 +40,9 @@ defmodule MimeMail.Params do def parse_kv(<>,:key,keyacc,acc) when c in [?\s,?\t,?\r,?\n,?;], do: parse_kv(rest,:key,keyacc,acc) # not allowed characters in key, skip def parse_kv(<>,:key,keyacc,acc), do: - parse_kv(rest,:quotedvalue,[],[{:"#{keyacc|>Enum.reverse|>to_string|>String.downcase}",""}|acc]) # enter in a quoted value, save key in res acc + parse_kv(rest,:quotedvalue,[],[{keyacc|>Enum.reverse|>to_string|>String.downcase|>String.to_existing_atom,""}|acc]) # enter in a quoted value, save key in res acc def parse_kv(<>,:key,keyacc,acc), do: - parse_kv(rest,:value,[],[{:"#{keyacc|>Enum.reverse|>to_string|>String.downcase}",""}|acc]) # enter in a simple value, save key in res acc + parse_kv(rest,:value,[],[{keyacc|>Enum.reverse|>to_string|>String.downcase|>String.to_existing_atom,""}|acc]) # enter in a simple value, save key in res acc def parse_kv(<>,:key,keyacc,acc), do: parse_kv(rest,:key,[c|keyacc],acc) # allowed char in key, add to key acc def parse_kv(<>,:quotedvalue,valueacc,acc), do: @@ -71,7 +71,7 @@ defmodule MimeMail.CTParams do end def decode_headers(%MimeMail{headers: headers}=mail) do parsed_mail_headers=for {k,{:raw,v}}<-headers,match?("content-"<>_,"#{k}"), do: {k,v|>MimeMail.header_value|>parse_header} - %{mail| headers: Enum.reduce(parsed_mail_headers,headers, fn {k,v},acc-> Dict.put(acc,k,v) end)} + %{mail| headers: Enum.reduce(parsed_mail_headers,headers, fn {k,v},acc-> List.keystore(acc,k,0,{k,v}) end)} end defimpl MimeMail.Header, for: Tuple do # a 2 tuple header is "value; key1=value1; key2=value2" @@ -129,7 +129,7 @@ defmodule MimeMail.Words do def decode_headers(%MimeMail{headers: headers}=mail) do parsed_mail_headers=for {k,{:raw,v}}<-headers, k in [:subject], do: {k,v|>MimeMail.header_value|>word_decode} - %{mail| headers: Enum.reduce(parsed_mail_headers,headers, fn {k,v},acc-> Dict.put(acc,k,v) end)} + %{mail| headers: Enum.reduce(parsed_mail_headers,headers, fn {k,v},acc-> List.keystore(acc,k,0,{k,v}) end)} end defimpl MimeMail.Header, for: BitString do # a 2 tuple header is "value; key1=value1; key2=value2" From 79ce0cc43eefa4f80732285409caf48351e58200 Mon Sep 17 00:00:00 2001 From: Heri Sim Date: Tue, 26 Jul 2016 11:14:53 +0700 Subject: [PATCH 3/3] Bump version Bump version as API has changed. Keys for header are now either string or atom. --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 25a37c8..376efe5 100644 --- a/mix.exs +++ b/mix.exs @@ -34,7 +34,7 @@ defmodule Mailibex.Mixfile do def project do [app: :mailibex, - version: "0.1.1", + version: "0.2.0", elixir: "~> 1.0", description: description, package: package,