root/openpgpsdk/trunk/src/armour.c

Revision 388 (checked in by ben, 7 years ago)

Refactor readers. Reading encrypted compressed data now works.

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