root/openpgpsdk/trunk/src/advanced/adv_armour.c

Revision 521 (checked in by rachel, 5 years ago)

Initial implementation of Validation of Document Signatures.
(Note: Does not yet interoperate with GPG)
Renamed validate_cb to more accurate name of validate_key_cb.
Implemented validate_data_cb to handle validation of documents & signed cleartext.
Added in error-handling when validation fails.
Turned off unwanted debug output.

Line 
1 /** \file
2  * \brief Code for dealing with ASCII-armoured packets
3  */
4
5 #include <openpgpsdk/configure.h>
6 #include <openpgpsdk/armour.h>
7 #include <openpgpsdk/util.h>
8 #include <openpgpsdk/crypto.h>
9 #include <openpgpsdk/create.h>
10 #include <openpgpsdk/signature.h>
11 #include <openpgpsdk/version.h>
12
13 #include <string.h>
14 #include <assert.h>
15
16 #include <openpgpsdk/final.h>
17
18 static int debug=0;
19
20 #define CRC24_INIT 0xb704ceL
21 #define CRC24_POLY 0x1864cfbL
22
23 /**
24  * \struct dearmour_arg_t
25  */
26 typedef struct
27     {
28     enum
29         {
30         OUTSIDE_BLOCK=0,
31         BASE64,
32         AT_TRAILER_NAME,
33         } state;
34     ops_parse_info_t *parse_info;
35     ops_boolean_t seen_nl:1;
36     ops_boolean_t prev_nl:1;
37     ops_boolean_t allow_headers_without_gap:1; /*!< allow headers in
38                                                   armoured data that
39                                                   are not separated
40                                                   from the data by a
41                                                   blank line */
42     ops_boolean_t allow_no_gap:1; /*!< allow no blank line at the
43                                        start of armoured data */
44     ops_boolean_t allow_trailing_whitespace:1; /*!< allow armoured
45                                                  stuff to have
46                                                  trailing whitespace
47                                                  where we wouldn't
48                                                  strictly expect it */
49
50     // base64 stuff
51     unsigned buffered;
52     unsigned char buffer[3];
53     ops_boolean_t eof64;
54     unsigned long checksum;
55     unsigned long read_checksum;
56     // unarmoured text blocks
57     unsigned char unarmoured[8192];
58     size_t num_unarmoured;
59     // pushed back data (stored backwards)
60     unsigned char *pushed_back;
61     unsigned npushed_back;
62     // armoured block headers
63     ops_headers_t headers;
64     } dearmour_arg_t;
65
66 // FIXME: move these to a common header
67 #define CB(cbinfo,t,pc) do { (pc)->tag=(t); if(ops_parse_cb((pc),(cbinfo)) == OPS_RELEASE_MEMORY) ops_parser_content_free(pc); } while(0)
68 #define ERR(cbinfo,err,code)    do { content.content.error.error=err; content.tag=OPS_PARSER_ERROR; ops_parse_cb(&content,(cbinfo)); OPS_ERROR(errors,code,err); return -1; } while(0)
69
70 static void push_back(dearmour_arg_t *arg,const unsigned char *buf,
71                       unsigned length)
72     {
73     unsigned n;
74
75     assert(!arg->pushed_back);
76     arg->pushed_back=malloc(length);
77     for(n=0 ; n < length ; ++n)
78         arg->pushed_back[n]=buf[length-n-1];
79     arg->npushed_back=length;
80     }
81    
82 static int read_char(dearmour_arg_t *arg,ops_error_t **errors,
83                      ops_reader_info_t *rinfo,
84                      ops_parse_cb_info_t *cbinfo,
85                      ops_boolean_t skip)
86     {
87     unsigned char c[1];
88
89     do
90         {
91         if(arg->npushed_back)
92             {
93             c[0]=arg->pushed_back[--arg->npushed_back];
94             if(!arg->npushed_back)
95                 {
96                 free(arg->pushed_back);
97                 arg->pushed_back=NULL;
98                 }
99             }
100         /* XXX: should ops_stacked_read exist? Shouldn't this be a limited_read? */
101         else if(ops_stacked_read(c,1,errors,rinfo,cbinfo) != 1)
102             return -1;
103         }
104     while(skip && c[0] == '\r');
105
106     arg->prev_nl=arg->seen_nl;
107     arg->seen_nl=c[0] == '\n';
108
109     return c[0];
110     }
111
112 static int eat_whitespace(int first,
113                           dearmour_arg_t *arg,ops_error_t **errors,
114                           ops_reader_info_t *rinfo,
115                           ops_parse_cb_info_t *cbinfo,
116                           ops_boolean_t skip)
117     {
118     int c=first;
119
120     while(c == ' ' || c == '\t')
121         c=read_char(arg,errors,rinfo,cbinfo,skip);
122
123     return c;
124     }
125
126 static int read_and_eat_whitespace(dearmour_arg_t *arg,
127                                    ops_error_t **errors,
128                                    ops_reader_info_t *rinfo,
129                                    ops_parse_cb_info_t *cbinfo,
130                                    ops_boolean_t skip)
131     {
132     int c;
133
134     do
135         c=read_char(arg,errors,rinfo,cbinfo,skip);
136     while(c == ' ' || c == '\t');
137
138     return c;
139     }
140
141 static void flush(dearmour_arg_t *arg,ops_parse_cb_info_t *cbinfo)
142     {
143     ops_parser_content_t content;
144
145     if(arg->num_unarmoured == 0)
146         return;
147
148     content.content.unarmoured_text.data=arg->unarmoured;
149     content.content.unarmoured_text.length=arg->num_unarmoured;
150     CB(cbinfo,OPS_PTAG_CT_UNARMOURED_TEXT,&content);
151     arg->num_unarmoured=0;
152     }
153
154 static int unarmoured_read_char(dearmour_arg_t *arg,ops_error_t **errors,
155                                 ops_reader_info_t *rinfo,
156                                 ops_parse_cb_info_t *cbinfo,
157                                 ops_boolean_t skip)
158     {
159     int c;
160
161     do
162         {
163         c=read_char(arg,errors,rinfo,cbinfo,ops_false);
164         if(c < 0)
165             return c;
166         arg->unarmoured[arg->num_unarmoured++]=c;
167         if(arg->num_unarmoured == sizeof arg->unarmoured)
168             flush(arg,cbinfo);
169         }
170     while(skip && c == '\r');
171
172     return c;
173     }
174
175 /**
176  * \param headers
177  * \param key
178  *
179  * \return header value if found, otherwise NULL
180  */
181 const char *ops_find_header(ops_headers_t *headers,const char *key)
182     {
183     unsigned n;
184
185     for(n=0 ; n < headers->nheaders ; ++n)
186         if(!strcmp(headers->headers[n].key,key))
187             return headers->headers[n].value;
188     return NULL;
189     }
190
191 /**
192  * \param dest
193  * \param src
194  */
195 void ops_dup_headers(ops_headers_t *dest,const ops_headers_t *src)
196     {
197     unsigned n;
198
199     dest->headers=malloc(src->nheaders*sizeof *dest->headers);
200     dest->nheaders=src->nheaders;
201
202     for(n=0 ; n < src->nheaders ; ++n)
203         {
204         dest->headers[n].key=strdup(src->headers[n].key);
205         dest->headers[n].value=strdup(src->headers[n].value);
206         }
207     }
208
209 /* Note that this skips CRs so implementations always see just
210    straight LFs as line terminators */
211 static int process_dash_escaped(dearmour_arg_t *arg,ops_error_t **errors,
212                                 ops_reader_info_t *rinfo,
213                                 ops_parse_cb_info_t *cbinfo)
214     {
215     ops_parser_content_t content;
216     ops_parser_content_t content2;
217     ops_signed_cleartext_body_t *body=&content.content.signed_cleartext_body;
218     ops_signed_cleartext_trailer_t *trailer
219         =&content2.content.signed_cleartext_trailer;
220     const char *hashstr;
221     ops_hash_t *hash;
222     int total;
223
224     hash=malloc(sizeof *hash);
225     hashstr=ops_find_header(&arg->headers,"Hash");
226     if(hashstr)
227         {
228         ops_hash_algorithm_t alg;
229
230         alg=ops_hash_algorithm_from_text(hashstr);
231         if(alg == OPS_HASH_UNKNOWN)
232             {
233             free(hash);
234             ERR(cbinfo,"Unknown hash algorithm",OPS_E_R_BAD_FORMAT);
235             }
236         ops_hash_any(hash,alg);
237         }
238     else
239         ops_hash_md5(hash);
240
241     hash->init(hash);
242
243     body->length=0;
244     total=0;
245     for( ; ; )
246         {
247         int c;
248         unsigned count;
249
250         if((c=read_char(arg,errors,rinfo,cbinfo,ops_true)) < 0)
251             return -1;
252         if(arg->prev_nl && c == '-')
253             {
254             if((c=read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
255                 return -1;
256             if(c != ' ')
257                 {
258                 /* then this had better be a trailer! */
259                 if(c != '-')
260                     ERR(cbinfo,"Bad dash-escaping",OPS_E_R_BAD_FORMAT);
261                 for(count=2 ; count < 5 ; ++count)
262                     {
263                     if((c=read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
264                         return -1;
265                     if(c != '-')
266                         ERR(cbinfo,"Bad dash-escaping (2)",OPS_E_R_BAD_FORMAT);
267                     }
268                 arg->state=AT_TRAILER_NAME;
269                 break;
270                 }
271             /* otherwise we read the next character */
272             if((c=read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
273                 return -1;
274             }
275         if(c == '\n' && body->length)
276             {
277             assert(memchr(body->data+1,'\n',body->length-1) == NULL);
278             if(body->data[0] == '\n')
279                 hash->add(hash,(unsigned char *)"\r",1);
280             hash->add(hash,body->data,body->length);
281             CB(cbinfo,OPS_PTAG_CT_SIGNED_CLEARTEXT_BODY,&content);
282             body->length=0;
283             }
284                
285         body->data[body->length++]=c;
286         ++total;
287         if(body->length == sizeof body->data)
288             {
289             CB(cbinfo,OPS_PTAG_CT_SIGNED_CLEARTEXT_BODY,&content);
290             body->length=0;
291             }
292         }
293
294     assert(body->data[0] == '\n');
295     assert(body->length == 1);
296     /* don't send that one character, because its part of the trailer. */
297
298     trailer->hash=hash;
299     CB(cbinfo,OPS_PTAG_CT_SIGNED_CLEARTEXT_TRAILER,&content2);
300
301     return total;
302     }
303
304 static void add_header(dearmour_arg_t *arg,const char *key,const char
305                        *value)
306     {
307     arg->headers.headers=realloc(arg->headers.headers,
308                                  (arg->headers.nheaders+1)
309                                  *sizeof *arg->headers.headers);
310     arg->headers.headers[arg->headers.nheaders].key=strdup(key);
311     arg->headers.headers[arg->headers.nheaders].value=strdup(value);
312     ++arg->headers.nheaders;
313     }
314
315 static int parse_headers(dearmour_arg_t *arg,ops_error_t **errors,
316                          ops_reader_info_t *rinfo,ops_parse_cb_info_t *cbinfo)
317     {
318     char *buf;
319     unsigned nbuf;
320     unsigned size;
321     ops_boolean_t first=ops_true;
322     ops_parser_content_t content;
323
324     buf=NULL;
325     nbuf=size=0;
326
327     for( ;  ; )
328         {
329         int c;
330
331         if((c=read_char(arg,errors,rinfo,cbinfo,ops_true)) < 0)
332             return -1;
333
334         if(c == '\n')
335             {
336             char *s;
337
338             if(nbuf == 0)
339                 break;
340
341             assert(nbuf < size);
342             buf[nbuf]='\0';
343
344             s=strchr(buf,':');
345             if(!s)
346                 if(!first && !arg->allow_headers_without_gap)
347                     // then we have seriously malformed armour
348                     ERR(cbinfo,"No colon in armour header",OPS_E_R_BAD_FORMAT);
349                 else
350                     {
351                     if(first &&
352                        !(arg->allow_headers_without_gap || arg->allow_no_gap))
353                         ERR(cbinfo,"No colon in armour header (2)",
354                             OPS_E_R_BAD_FORMAT);
355                     // then we have a nasty armoured block with no
356                     // headers, not even a blank line.
357                     buf[nbuf]='\n';
358                     push_back(arg,(unsigned char *)buf,nbuf+1);
359                     break;
360                     }
361             else
362                 {
363                 *s='\0';
364                 if(s[1] != ' ')
365                     ERR(cbinfo,"No space in armour header",OPS_E_R_BAD_FORMAT);
366                 add_header(arg,buf,s+2);
367                 nbuf=0;
368                 }
369             first=ops_false;
370             }
371         else
372             {
373             if(size <= nbuf+1)
374                 {
375                 size+=size+80;
376                 buf=realloc(buf,size);
377                 }
378             buf[nbuf++]=c;
379             }
380         }
381
382     free(buf);
383
384     return 1;
385     }
386
387 static int read4(dearmour_arg_t *arg,ops_error_t **errors,
388                  ops_reader_info_t *rinfo,ops_parse_cb_info_t *cbinfo,
389                  int *pc,unsigned *pn,unsigned long *pl)
390     {
391     int n,c;
392     unsigned long l=0;
393
394     for(n=0 ; n < 4 ; ++n)
395         {
396         c=read_char(arg,errors,rinfo,cbinfo,ops_true);
397         if(c < 0)
398             {
399             arg->eof64=ops_true;
400             return -1;
401             }
402         if(c == '-')
403             break;
404         if(c == '=')
405             break;
406         l <<= 6;
407         if(c >= 'A' && c <= 'Z')
408             l+=c-'A';
409         else if(c >= 'a' && c <= 'z')
410             l+=c-'a'+26;
411         else if(c >= '0' && c <= '9')
412             l+=c-'0'+52;
413         else if(c == '+')
414             l+=62;
415         else if(c == '/')
416             l+=63;
417         else
418             {
419             --n;
420             l >>= 6;
421             }
422         }
423
424     *pc=c;
425     *pn=n;
426     *pl=l;
427
428     return 4;
429     }
430
431 static unsigned crc24(unsigned checksum,unsigned char c)
432     {
433     unsigned i;
434
435     checksum ^= c << 16;
436     for(i=0 ; i < 8 ; i++)
437         {
438         checksum <<= 1;
439         if(checksum & 0x1000000)
440             checksum ^= CRC24_POLY;
441         }
442     return checksum&0xffffffL;
443     }
444
445 static int decode64(dearmour_arg_t *arg,ops_error_t **errors,
446                     ops_reader_info_t *rinfo,ops_parse_cb_info_t *cbinfo)
447     {
448     unsigned n;
449     int n2;
450     unsigned long l;
451     ops_parser_content_t content;
452     int c;
453     int ret;
454
455     assert(arg->buffered == 0);
456
457     ret=read4(arg,errors,rinfo,cbinfo,&c,&n,&l);
458     if(ret < 0)
459         ERR(cbinfo,"Badly formed base64",OPS_E_R_BAD_FORMAT);
460
461     if(n == 3)
462         {
463         if(c != '=')
464             ERR(cbinfo,"Badly terminated base64 (2)",OPS_E_R_BAD_FORMAT);
465         arg->buffered=2;
466         arg->eof64=ops_true;
467         l >>= 2;
468         }
469     else if(n == 2)
470         {
471         if(c != '=')
472             ERR(cbinfo,"Badly terminated base64 (3)",OPS_E_R_BAD_FORMAT);
473         arg->buffered=1;
474         arg->eof64=ops_true;
475         l >>= 4;
476         c=read_char(arg,errors,rinfo,cbinfo,ops_false);
477         if(c != '=')
478             ERR(cbinfo,"Badly terminated base64",OPS_E_R_BAD_FORMAT);
479         }
480     else if(n == 0)
481         {
482         if(!arg->prev_nl || c != '=')
483             ERR(cbinfo,"Badly terminated base64 (4)",OPS_E_R_BAD_FORMAT);
484         arg->buffered=0;
485         }
486     else
487         {
488         assert(n == 4);
489         arg->buffered=3;
490         assert(c != '-' && c != '=');
491         }
492
493     if(arg->buffered < 3 && arg->buffered > 0)
494         {
495         // then we saw padding
496         assert(c == '=');
497         c=read_and_eat_whitespace(arg,errors,rinfo,cbinfo,ops_true);
498         if(c != '\n')
499             ERR(cbinfo,"No newline at base64 end",OPS_E_R_BAD_FORMAT);
500         c=read_char(arg,errors,rinfo,cbinfo,ops_false);
501         if(c != '=')
502             ERR(cbinfo,"No checksum at base64 end",OPS_E_R_BAD_FORMAT);
503         }
504
505     if(c == '=')
506         {
507         // now we are at the checksum
508         ret=read4(arg,errors,rinfo,cbinfo,&c,&n,&arg->read_checksum);
509         if(ret < 0 || n != 4)
510             ERR(cbinfo,"Error in checksum",OPS_E_R_BAD_FORMAT);
511         c=read_char(arg,errors,rinfo,cbinfo,ops_true);
512         if(arg->allow_trailing_whitespace)
513             c=eat_whitespace(c,arg,errors,rinfo,cbinfo,ops_true);
514         if(c != '\n')
515             ERR(cbinfo,"Badly terminated checksum",OPS_E_R_BAD_FORMAT);
516         c=read_char(arg,errors,rinfo,cbinfo,ops_false);
517         if(c != '-')
518             ERR(cbinfo,"Bad base64 trailer (2)",OPS_E_R_BAD_FORMAT);
519         }
520
521     if(c == '-')
522         {
523         for(n=0 ; n < 4 ; ++n)
524             if(read_char(arg,errors,rinfo,cbinfo,ops_false) != '-')
525                 ERR(cbinfo,"Bad base64 trailer",OPS_E_R_BAD_FORMAT);
526         arg->eof64=ops_true;
527         }
528     else
529         assert(arg->buffered);
530
531     for(n=0 ; n < arg->buffered ; ++n)
532         {
533         arg->buffer[n]=l;
534         l >>= 8;
535         }
536
537     for(n2=arg->buffered-1 ; n2 >= 0 ; --n2)
538         arg->checksum=crc24(arg->checksum,arg->buffer[n2]);
539
540     if(arg->eof64 && arg->read_checksum != arg->checksum)
541         ERR(cbinfo,"Checksum mismatch",OPS_E_R_BAD_FORMAT);
542
543     return 1;
544     }
545
546 static void base64(dearmour_arg_t *arg)
547     {
548     arg->state=BASE64;
549     arg->checksum=CRC24_INIT;
550     arg->eof64=ops_false;
551     arg->buffered=0;
552     }
553
554 // This reader is rather strange in that it can generate callbacks for
555 // content - this is because plaintext is not encapsulated in PGP
556 // packets... it also calls back for the text between the blocks.
557
558 static int armoured_data_reader(void *dest_,size_t length,ops_error_t **errors,
559                                 ops_reader_info_t *rinfo,
560                                 ops_parse_cb_info_t *cbinfo)
561      {
562      dearmour_arg_t *arg=ops_reader_get_arg(rinfo);
563      ops_parser_content_t content;
564      int ret;
565      ops_boolean_t first;
566      unsigned char *dest=dest_;
567      int saved=length;
568
569      if(arg->eof64 && !arg->buffered)
570          assert(arg->state == OUTSIDE_BLOCK || arg->state == AT_TRAILER_NAME);
571
572      while(length > 0)
573          {
574          unsigned count;
575          unsigned n;
576          char buf[1024];
577          int c;
578
579          flush(arg,cbinfo);
580          switch(arg->state)
581              {
582          case OUTSIDE_BLOCK:
583              /* This code returns EOF rather than EARLY_EOF because if
584                 we don't see a header line at all, then it is just an
585                 EOF (and not a BLOCK_END) */
586              while(!arg->seen_nl)
587                  if((c=unarmoured_read_char(arg,errors,rinfo,cbinfo,ops_true)) < 0)
588                      return 0;
589
590              /* flush at this point so we definitely have room for the
591                 header, and so we can easily erase it from the buffer */
592              flush(arg,cbinfo);
593              /* Find and consume the 5 leading '-' */
594              for(count=0 ; count < 5 ; ++count)
595                  {
596                  if((c=unarmoured_read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
597                      return 0;
598                  if(c != '-')
599                      goto reloop;
600                  }
601
602              /* Now find the block type */
603              for(n=0 ; n < sizeof buf-1 ; )
604                  {
605                  if((c=unarmoured_read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
606                      return 0;
607                  if(c == '-')
608                      goto got_minus;
609                  buf[n++]=c;
610                  }
611              /* then I guess this wasn't a proper header */
612              break;
613
614          got_minus:
615              buf[n]='\0';
616
617              /* Consume trailing '-' */
618              for(count=1 ; count < 5 ; ++count)
619                  {
620                  if((c=unarmoured_read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
621                      return 0;
622                  if(c != '-')
623                      /* wasn't a header after all */
624                      goto reloop;
625                  }
626
627              /* Consume final NL */
628              if((c=unarmoured_read_char(arg,errors,rinfo,cbinfo,ops_true)) < 0)
629                  return 0;
630              if(arg->allow_trailing_whitespace)
631                  if((c=eat_whitespace(c,arg,errors,rinfo,cbinfo,
632                                       ops_true)) < 0)
633                     return 0;
634              if(c != '\n')
635                  /* wasn't a header line after all */
636                  break;
637
638              /* Now we've seen the header, scrub it from the buffer */
639              arg->num_unarmoured=0;
640
641              /* But now we've seen a header line, then errors are
642                 EARLY_EOF */
643              if((ret=parse_headers(arg,errors,rinfo,cbinfo)) <= 0)
644                  return -1;
645
646              if(!strcmp(buf,"BEGIN PGP SIGNED MESSAGE"))
647                  {
648                  ops_dup_headers(&content.content.signed_cleartext_header.headers,&arg->headers);
649                  CB(cbinfo,OPS_PTAG_CT_SIGNED_CLEARTEXT_HEADER,&content);
650
651                  ret=process_dash_escaped(arg,errors,rinfo,cbinfo);
652                  if(ret <= 0)
653                      return ret;
654                  }
655              else
656                  {
657                  content.content.armour_header.type=buf;
658                  content.content.armour_header.headers=arg->headers;
659                  memset(&arg->headers,'\0',sizeof arg->headers);
660                  CB(cbinfo,OPS_PTAG_CT_ARMOUR_HEADER,&content);
661                  base64(arg);
662                  }
663              break;
664
665          case BASE64:
666              first=ops_true;
667              while(length > 0)
668                  {
669                  if(!arg->buffered)
670                      {
671                      if(!arg->eof64)
672                          {
673                          ret=decode64(arg,errors,rinfo,cbinfo);
674                          if(ret <= 0)
675                              return ret;
676                          }
677                      if(!arg->buffered)
678                          {
679                          assert(arg->eof64);
680                          if(first)
681                              {
682                              arg->state=AT_TRAILER_NAME;
683                              goto reloop;
684                              }
685                          return -1;
686                          }
687                      }
688
689                  assert(arg->buffered);
690                  *dest=arg->buffer[--arg->buffered];
691                  ++dest;
692                  --length;
693                  first=ops_false;
694                  }
695              if(arg->eof64 && !arg->buffered)
696                  arg->state=AT_TRAILER_NAME;
697              break;
698
699          case AT_TRAILER_NAME:
700              for(n=0 ; n < sizeof buf-1 ; )
701                  {
702                  if((c=read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
703                      return -1;
704                  if(c == '-')
705                      goto got_minus2;
706                  buf[n++]=c;
707                  }
708              /* then I guess this wasn't a proper trailer */
709              ERR(cbinfo,"Bad ASCII armour trailer",OPS_E_R_BAD_FORMAT);
710              break;
711
712          got_minus2:
713              buf[n]='\0';
714
715              /* Consume trailing '-' */
716              for(count=1 ; count < 5 ; ++count)
717                  {
718                  if((c=read_char(arg,errors,rinfo,cbinfo,ops_false)) < 0)
719                      return -1;
720                  if(c != '-')
721                      /* wasn't a trailer after all */
722                      ERR(cbinfo,"Bad ASCII armour trailer (2)",
723                          OPS_E_R_BAD_FORMAT);
724                  }
725
726              /* Consume final NL */
727              if((c=read_char(arg,errors,rinfo,cbinfo,ops_true)) < 0)
728                  return -1;
729              if(arg->allow_trailing_whitespace)
730                  if((c=eat_whitespace(c,arg,errors,rinfo,cbinfo,
731                                       ops_true)) < 0)
732                     return 0;
733              if(c != '\n')
734                  /* wasn't a trailer line after all */
735                  ERR(cbinfo,"Bad ASCII armour trailer (3)",OPS_E_R_BAD_FORMAT);
736
737              if(!strncmp(buf,"BEGIN ",6))
738                  {
739                  if((ret=parse_headers(arg,errors,rinfo,cbinfo)) <= 0)
740                      return ret;
741                  content.content.armour_header.type=buf;
742                  content.content.armour_header.headers=arg->headers;
743                  memset(&arg->headers,'\0',sizeof arg->headers);
744                  CB(cbinfo,OPS_PTAG_CT_ARMOUR_HEADER,&content);
745                  base64(arg);
746                  }
747              else
748                  {
749                  content.content.armour_trailer.type=buf;
750                  CB(cbinfo,OPS_PTAG_CT_ARMOUR_TRAILER,&content);
751                 arg->state=OUTSIDE_BLOCK;
752                 }
753             break;
754             }
755     reloop:
756         continue;
757         }
758
759     return saved;
760     }
761
762 static void armoured_data_destroyer(ops_reader_info_t *rinfo)
763     { free(ops_reader_get_arg(rinfo)); }
764
765 /**
766  * \brief
767  * \param parse_info Usual structure containing information about to how to do the parse
768  * \param without_gap Allow headers in armoured data that are not separated from the data by a blank line
769  * \param no_gap Allow no blank line at the start of armoured data
770  * \param trailing_whitespace Allow armoured data to have trailing whitespace where we strictly would not expect it
771  */
772 void ops_reader_push_dearmour(ops_parse_info_t *parse_info,
773                               ops_boolean_t without_gap,
774                               ops_boolean_t no_gap,
775                               ops_boolean_t trailing_whitespace)
776     {
777     dearmour_arg_t *arg;
778
779     arg=ops_mallocz(sizeof *arg);
780     arg->seen_nl=ops_true;
781     arg->allow_headers_without_gap=without_gap;
782     arg->allow_no_gap=no_gap;
783     arg->allow_trailing_whitespace=trailing_whitespace;
784
785     ops_reader_push(parse_info,armoured_data_reader,armoured_data_destroyer,arg);
786     }
787
788 /**
789  * \param parse_info
790  */
791 void ops_reader_pop_dearmour(ops_parse_info_t *parse_info)
792     {
793     dearmour_arg_t *arg=ops_reader_get_arg(ops_parse_get_rinfo(parse_info));
794
795     free(arg);
796     }
797
798 /**
799  * \struct dash_escaped_arg_t
800  */
801 typedef struct
802     {
803     ops_boolean_t seen_nl:1;
804     ops_boolean_t seen_cr:1;
805     ops_create_signature_t *sig;
806     ops_memory_t *trailing;
807     } dash_escaped_arg_t;
808
809 static ops_boolean_t dash_escaped_writer(const unsigned char *src,
810                                          unsigned length,
811                                          ops_error_t **errors,
812                                          ops_writer_info_t *winfo)
813     {
814     dash_escaped_arg_t *arg=ops_writer_get_arg(winfo);
815     unsigned n;
816
817     if (debug)
818         {
819         unsigned int i=0;
820         fprintf(stderr,"dash_escaped_writer writing %d:\n", length);
821         for (i=0; i<length; i++)
822             {
823             fprintf(stderr,"0x%02x ", src[i]);
824             if (!((i+1) % 16))
825                 fprintf(stderr,"\n");
826             else if (!((i+1) % 8))
827                 fprintf(stderr,"  ");
828             }
829         fprintf(stderr,"\n");
830         }
831
832     // XXX: make this efficient
833     for(n=0 ; n < length ; ++n)
834         {
835         unsigned l;
836
837         if(arg->seen_nl)
838             {
839             if(src[n] == '-' && !ops_stacked_write("- ",2,errors,winfo))
840                 return ops_false;
841             arg->seen_nl=ops_false;
842             }
843
844         arg->seen_nl=src[n] == '\n';
845
846         if(arg->seen_nl && !arg->seen_cr)
847             {
848             if(!ops_stacked_write("\r",1,errors,winfo))
849                 return ops_false;
850             ops_signature_add_data(arg->sig,"\r",1);
851             }
852
853         arg->seen_cr=src[n] == '\r';
854
855         if(!ops_stacked_write(&src[n],1,errors,winfo))
856             return ops_false;
857
858         /* trailing whitespace isn't included in the signature */
859         if(src[n] == ' ' || src[n] == '\t')
860             ops_memory_add(arg->trailing,&src[n],1);
861         else
862             {
863             if((l=ops_memory_get_length(arg->trailing)))
864                 {
865                 if(!arg->seen_nl && !arg->seen_cr)
866                     ops_signature_add_data(arg->sig,
867                                            ops_memory_get_data(arg->trailing),
868                                            l);
869                 ops_memory_clear(arg->trailing);
870                 }
871             ops_signature_add_data(arg->sig,&src[n],1);
872             }
873         }
874
875     return ops_true;
876     }
877
878 /**
879  * \param winfo
880  */
881 void dash_escaped_destroyer(ops_writer_info_t *winfo)
882     {
883     dash_escaped_arg_t *arg=ops_writer_get_arg(winfo);
884
885     ops_memory_free(arg->trailing);
886     free(arg);
887     }
888
889 // XXX: should return errors.
890 /**
891  * \param info
892  * \param sig
893  * \todo should return errors
894  */
895 void ops_writer_push_dash_escaped(ops_create_info_t *info,
896                                   ops_create_signature_t *sig)
897     {
898     static char header[]="-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: ";
899     const char *hash=ops_text_from_hash(ops_signature_get_hash(sig));
900     dash_escaped_arg_t *arg=ops_mallocz(sizeof *arg);
901
902     ops_write(header,sizeof header-1,info);
903     ops_write(hash,strlen(hash),info);
904     ops_write("\r\n\r\n",4,info);
905     arg->seen_nl=ops_true;
906     arg->sig=sig;
907     arg->trailing=ops_memory_new();
908     ops_writer_push(info,dash_escaped_writer,NULL,dash_escaped_destroyer,arg);
909     }
910
911 /**
912  * \struct base64_arg_t
913  */
914 typedef struct
915     {
916     unsigned pos;
917     unsigned char t;
918     unsigned checksum;
919     } base64_arg_t;
920
921 static char b64map[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
922 "0123456789+/";
923
924 static ops_boolean_t base64_writer(const unsigned char *src,
925                                    unsigned length,ops_error_t **errors,
926                                    ops_writer_info_t *winfo)
927     {
928     base64_arg_t *arg=ops_writer_get_arg(winfo);
929     unsigned n;
930
931     for(n=0 ; n < length ; )
932         {
933         arg->checksum=crc24(arg->checksum,src[n]);
934         if(arg->pos == 0)
935             {
936             /* XXXXXX00 00000000 00000000 */
937             if(!ops_stacked_write(&b64map[src[n] >> 2],1,errors,winfo))
938                 return ops_false;
939
940             /* 000000XX xxxx0000 00000000 */
941             arg->t=(src[n++]&3) << 4;
942             arg->pos=1;
943             }
944         else if(arg->pos == 1)
945             {
946             /* 000000xx XXXX0000 00000000 */
947             arg->t+=src[n] >> 4;
948             if(!ops_stacked_write(&b64map[arg->t],1,errors,winfo))
949                 return ops_false;
950
951             /* 00000000 0000XXXX xx000000 */
952             arg->t=(src[n++]&0xf) << 2;
953             arg->pos=2;
954             }
955         else if(arg->pos == 2)
956             {
957             /* 00000000 0000xxxx XX000000 */
958             arg->t+=src[n] >> 6;
959             if(!ops_stacked_write(&b64map[arg->t],1,errors,winfo))
960                 return ops_false;
961
962             /* 00000000 00000000 00XXXXXX */
963             if(!ops_stacked_write(&b64map[src[n++]&0x3f],1,errors,winfo))
964                 return ops_false;
965
966             arg->pos=0;
967             }
968         }
969
970     return ops_true;
971     }
972
973 static ops_boolean_t signature_finaliser(ops_error_t **errors,
974                                          ops_writer_info_t *winfo)
975     {
976     base64_arg_t *arg=ops_writer_get_arg(winfo);
977     static char trailer[]="\r\n-----END PGP SIGNATURE-----\r\n";
978     unsigned char c[3];
979
980     if(arg->pos)
981         {
982         if(!ops_stacked_write(&b64map[arg->t],1,errors,winfo))
983             return ops_false;
984         if(arg->pos == 1 && !ops_stacked_write("==",2,errors,winfo))
985             return ops_false;
986         if(arg->pos == 2 && !ops_stacked_write("=",1,errors,winfo))
987             return ops_false;
988         }
989     /* Ready for the checksum */
990     if(!ops_stacked_write("\r\n=",3,errors,winfo))
991         return ops_false;
992
993     arg->pos=0; /* get ready to write the checksum */
994
995     c[0]=arg->checksum >> 16;
996     c[1]=arg->checksum >> 8;
997     c[2]=arg->checksum;
998     /* push the checksum through our own writer */
999     if(!base64_writer(c,3,errors,winfo))
1000         return ops_false;
1001
1002     return ops_stacked_write(trailer,sizeof trailer-1,errors,winfo);
1003     }
1004
1005 /**
1006  * \struct linebreak_arg_t
1007  */
1008 typedef struct
1009     {
1010     unsigned pos;
1011     } linebreak_arg_t;
1012
1013 #define BREAKPOS        76
1014
1015 static ops_boolean_t linebreak_writer(const unsigned char *src,
1016                                          unsigned length,
1017                                          ops_error_t **errors,
1018                                          ops_writer_info_t *winfo)
1019     {
1020     linebreak_arg_t *arg=ops_writer_get_arg(winfo);
1021     unsigned n;
1022
1023     for(n=0 ; n < length ; ++n,++arg->pos)
1024         {
1025         if(src[n] == '\r' || src[n] == '\n')
1026             arg->pos=0;
1027
1028         if(arg->pos == BREAKPOS)
1029             {
1030             if(!ops_stacked_write("\r\n",2,errors,winfo))
1031                 return ops_false;
1032             arg->pos=0;
1033             }
1034         if(!ops_stacked_write(&src[n],1,errors,winfo))
1035             return ops_false;
1036         }
1037
1038     return ops_true;
1039     }
1040
1041 // XXX: should return errors.
1042 /**
1043  * \param info
1044  */
1045 void ops_writer_switch_to_signature(ops_create_info_t *info)
1046     {
1047     static char header[]="\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: "
1048         OPS_VERSION_STRING "\r\n\r\n";
1049     base64_arg_t *base64;
1050
1051     ops_writer_pop(info);
1052     ops_write(header,sizeof header-1,info);
1053
1054     ops_writer_push(info,linebreak_writer,NULL,ops_writer_generic_destroyer,
1055                     ops_mallocz(sizeof(linebreak_arg_t)));
1056
1057     base64=ops_mallocz(sizeof *base64);
1058     base64->checksum=CRC24_INIT;
1059     ops_writer_push(info,base64_writer,signature_finaliser,
1060                     ops_writer_generic_destroyer,base64);
1061     }
Note: See TracBrowser for help on using the browser.