Delta Chat Core C-Library
mraheader.c
1 /*******************************************************************************
2  *
3  * Delta Chat Core
4  * Copyright (C) 2017 Björn Petersen
5  * Contact: r10s@b44t.com, http://b44t.com
6  *
7  * This program is free software: you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation, either version 3 of the License, or (at your option) any later
10  * version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see http://www.gnu.org/licenses/ .
19  *
20  ******************************************************************************/
21 
22 
23 #include <ctype.h>
24 #include "mrmailbox_internal.h"
25 #include "mraheader.h"
26 #include "mrapeerstate.h"
27 #include "mrmimeparser.h"
28 
29 
33 void mraheader_empty(mraheader_t* ths)
34 {
35  if( ths == NULL ) {
36  return;
37  }
38 
39  ths->m_prefer_encrypt = 0;
40 
41  free(ths->m_addr);
42  ths->m_addr = NULL;
43 
44  if( ths->m_public_key->m_binary ) {
45  mrkey_unref(ths->m_public_key);
46  ths->m_public_key = mrkey_new();
47  }
48 }
49 
50 
51 /*******************************************************************************
52  * Render Autocrypt Header
53  ******************************************************************************/
54 
55 
59 char* mraheader_render(const mraheader_t* ths)
60 {
61  int success = 0;
62  char* keybase64_wrapped = NULL;
63  mrstrbuilder_t ret;
64  mrstrbuilder_init(&ret);
65 
66  if( ths==NULL || ths->m_addr==NULL || ths->m_public_key->m_binary==NULL || ths->m_public_key->m_type!=MR_PUBLIC ) {
67  goto cleanup;
68  }
69 
70  mrstrbuilder_cat(&ret, "addr=");
71  mrstrbuilder_cat(&ret, ths->m_addr);
72  mrstrbuilder_cat(&ret, "; ");
73 
74  if( ths->m_prefer_encrypt==MRA_PE_MUTUAL ) {
75  mrstrbuilder_cat(&ret, "prefer-encrypt=mutual; ");
76  }
77 
78  mrstrbuilder_cat(&ret, "keydata= "); /* the trailing space together with mr_insert_breaks() allows a proper transport */
79 
80  /* adds a whitespace every 78 characters, this allows libEtPan to wrap the lines according to RFC 5322
81  (which may insert a linebreak before every whitespace) */
82  if( (keybase64_wrapped = mrkey_render_base64(ths->m_public_key, 78, " ", 0/*no checksum*/)) == NULL ) {
83  goto cleanup;
84  }
85 
86  mrstrbuilder_cat(&ret, keybase64_wrapped);
87 
88  success = 1;
89 
90 cleanup:
91  if( !success ) { free(ret.m_buf); ret.m_buf = NULL; }
92  free(keybase64_wrapped);
93  return ret.m_buf; /* NULL on errors, this may happen for various reasons */
94 }
95 
96 
97 /*******************************************************************************
98  * Parse Autocrypt Header
99  ******************************************************************************/
100 
101 
102 static int add_attribute(mraheader_t* ths, const char* name, const char* value /*may be NULL*/)
103 {
104  /* returns 0 if the attribute will result in an invalid header, 1 if the attribute is okay */
105  if( strcasecmp(name, "addr")==0 )
106  {
107  if( value == NULL
108  || strlen(value) < 3 || strchr(value, '@')==NULL || strchr(value, '.')==NULL /* rough check if email-address is valid */
109  || ths->m_addr /* email already given */ ) {
110  return 0;
111  }
112  ths->m_addr = mr_normalize_addr(value);
113  return 1;
114  }
115  #if 0 /* autoctypt 11/2017 no longer uses the type attribute and it will make the autocrypt header invalid */
116  else if( strcasecmp(name, "type")==0 )
117  {
118  if( value == NULL ) {
119  return 0; /* attribute with no value results in an invalid header */
120  }
121  if( strcasecmp(value, "1")==0 || strcasecmp(value, "0" /*deprecated*/)==0 || strcasecmp(value, "p" /*deprecated*/)==0 ) {
122  return 1; /* PGP-type */
123  }
124  return 0; /* unknown types result in an invalid header */
125  }
126  #endif
127  else if( strcasecmp(name, "prefer-encrypt")==0 )
128  {
129  if( value && strcasecmp(value, "mutual")==0 ) {
130  ths->m_prefer_encrypt = MRA_PE_MUTUAL;
131  return 1;
132  }
133  return 1; /* An Autocrypt level 0 client that sees the attribute with any other value (or that does not see the attribute at all) should interpret the value as nopreference.*/
134  }
135  else if( strcasecmp(name, "keydata")==0 )
136  {
137  if( value == NULL
138  || ths->m_public_key->m_binary || ths->m_public_key->m_bytes ) {
139  return 0; /* there is already a k*/
140  }
141  return mrkey_set_from_base64(ths->m_public_key, value, MR_PUBLIC);
142  }
143  else if( name[0]=='_' )
144  {
145  /* Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored */
146  return 1;
147  }
148 
149  /* Autocrypt-Level0: unknown attribute, treat the header as invalid */
150  return 0;
151 }
152 
153 
157 int mraheader_set_from_string(mraheader_t* ths, const char* header_str__)
158 {
159  /* according to RFC 5322 (Internet Message Format), the given string may contain `\r\n` before any whitespace.
160  we can ignore this issue as
161  (a) no key or value is expected to contain spaces,
162  (b) for the key, non-base64-characters are ignored and
163  (c) for parsing, we ignore `\r\n` as well as tabs for spaces */
164  #define AHEADER_WS "\t\r\n "
165  char *header_str = NULL;
166  char *p, *beg_attr_name, *after_attr_name, *beg_attr_value;
167  int success = 0;
168 
169  mraheader_empty(ths);
170  ths->m_prefer_encrypt = MRA_PE_NOPREFERENCE; /* value to use if the prefer-encrypted header is missing */
171 
172  if( ths == NULL || header_str__ == NULL ) {
173  goto cleanup;
174  }
175 
176  header_str = safe_strdup(header_str__);
177  p = header_str;
178  while( *p )
179  {
180  p += strspn(p, AHEADER_WS "=;"); /* forward to first attribute name beginning */
181  beg_attr_name = p;
182  beg_attr_value = NULL;
183  p += strcspn(p, AHEADER_WS "=;"); /* get end of attribute name (an attribute may have no value) */
184  if( p != beg_attr_name )
185  {
186  /* attribute found */
187  after_attr_name = p;
188  p += strspn(p, AHEADER_WS); /* skip whitespace between attribute name and possible `=` */
189  if( *p == '=' )
190  {
191  p += strspn(p, AHEADER_WS "="); /* skip spaces and equal signs */
192 
193  /* read unquoted attribute value until the first semicolon */
194  beg_attr_value = p;
195  p += strcspn(p, ";");
196  if( *p != '\0' ) {
197  *p = '\0';
198  p++;
199  }
200  mr_trim(beg_attr_value);
201  }
202  else
203  {
204  p += strspn(p, AHEADER_WS ";");
205  }
206  *after_attr_name = '\0';
207  if( !add_attribute(ths, beg_attr_name, beg_attr_value) ) {
208  goto cleanup; /* a bad attribute makes the whole header invalid */
209  }
210  }
211  }
212 
213  /* all needed data found? */
214  if( ths->m_addr && ths->m_public_key->m_binary ) {
215  success = 1;
216  }
217 
218 cleanup:
219  free(header_str);
220  if( !success ) { mraheader_empty(ths); }
221  return success;
222 }
223 
224 
225 /*******************************************************************************
226  * Main interface
227  ******************************************************************************/
228 
229 
233 mraheader_t* mraheader_new()
234 {
235  mraheader_t* ths = NULL;
236 
237  if( (ths=calloc(1, sizeof(mraheader_t)))==NULL ) {
238  exit(37); /* cannot allocate little memory, unrecoverable error */
239  }
240 
241  ths->m_public_key = mrkey_new();
242 
243  return ths;
244 }
245 
246 
250 void mraheader_unref(mraheader_t* ths)
251 {
252  if( ths==NULL ) {
253  return;
254  }
255 
256  free(ths->m_addr);
257  mrkey_unref(ths->m_public_key);
258  free(ths);
259 }
260 
261 
265 mraheader_t* mraheader_new_from_imffields(const char* wanted_from, const struct mailimf_fields* header)
266 {
267  clistiter* cur;
268  mraheader_t* fine_header = NULL;
269 
270  if( wanted_from == NULL || header == NULL ) {
271  return 0;
272  }
273 
274  for( cur = clist_begin(header->fld_list); cur!=NULL ; cur=clist_next(cur) )
275  {
276  struct mailimf_field* field = (struct mailimf_field*)clist_content(cur);
277  if( field && field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD )
278  {
279  struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
280  if( optional_field && optional_field->fld_name && strcasecmp(optional_field->fld_name, "Autocrypt")==0 )
281  {
282  /* header found, check if it is valid and matched the wanted address */
283  mraheader_t* test = mraheader_new();
284  if( !mraheader_set_from_string(test, optional_field->fld_value)
285  || strcasecmp(test->m_addr, wanted_from)!=0 ) {
286  mraheader_unref(test);
287  test = NULL;
288  }
289 
290  if( fine_header == NULL ) {
291  fine_header = test; /* may still be NULL */
292  }
293  else if( test ) {
294  mraheader_unref(fine_header);
295  mraheader_unref(test);
296  return NULL; /* more than one valid header for the same address results in an error, see Autocrypt Level 1 */
297  }
298  }
299  }
300  }
301 
302  return fine_header; /* may be NULL */
303 }
304