root/WizardsToolkit/trunk/utilities/content.c

Revision 511, 24.1 KB (checked in by cristy, 3 weeks ago)
Line 
1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                CCCC   OOO   N   N  TTTTT  EEEEE  N   N  TTTTT               %
6%               C      O   O  NN  N    T    E      NN  N    T                 %
7%               C      O   O  N N N    T    EEE    N N N    T                 %
8%               C      O   O  N  NN    T    E      N  NN    T                 %
9%                CCCC   OOO   N   N    T    EEEEE  N   N    T                 %
10%                                                                             %
11%                      Wizard's Toolkit Content Methods                       %
12%                                                                             %
13%                             Software Design                                 %
14%                               John Cristy                                   %
15%                               March 2003                                    %
16%                                                                             %
17%                                                                             %
18%  Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization      %
19%  dedicated to making software imaging solutions freely available.           %
20%                                                                             %
21%  You may not use this file except in compliance with the License.  You may  %
22%  obtain a copy of the License at                                            %
23%                                                                             %
24%    http://www.wizards-toolkit.org/script/license.php                        %
25%                                                                             %
26%  Unless required by applicable law or agreed to in writing, software        %
27%  distributed under the License is distributed on an "AS IS" BASIS,          %
28%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
29%  See the License for the specific language governing permissions and        %
30%  limitations under the License.                                             %
31%                                                                             %
32%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33%
34%
35*/
36
37/*
38  Include declarations.
39*/
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <time.h>
44#include "wizard/studio.h"
45#include "wizard/WizardsToolkit.h"
46#include "wizard/blob-private.h"
47#include "wizard/exception-private.h"
48#include "content.h"
49
50/*
51%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
52%                                                                             %
53%                                                                             %
54%                                                                             %
55%   A c q u i r e C i p h e r C o n t e n t                                   %
56%                                                                             %
57%                                                                             %
58%                                                                             %
59%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
60%
61%  AcquireContentInfo() allocates the ContentInfo structure.
62%
63%  The format of the AcquireContentInfo method is:
64%
65%      ContentInfo *AcquireContentInfo(void)
66%
67*/
68WizardExport ContentInfo *AcquireContentInfo(void)
69{
70  ContentInfo
71    *content_info;
72
73  unsigned long
74    version;
75
76  content_info=(ContentInfo *) AcquireWizardMemory(sizeof(*content_info));
77  if (content_info == (ContentInfo *) NULL)
78    ThrowWizardFatalError(CipherDomain,MemoryError);
79  (void) ResetWizardMemory(content_info,0,sizeof(*content_info));
80  content_info->cipher=AESCipher;
81  content_info->mode=CTRMode;
82  content_info->authenticate_method=SecretAuthenticateMethod;
83  content_info->key_hash=SHA256Hash;
84  content_info->key_length=512;
85  content_info->entropy=BZIPEntropy;
86  content_info->level=6;
87  content_info->hmac=SHA256Hash;
88  content_info->random_hash=SHA256Hash;
89  content_info->chunksize=262144;
90  content_info->timestamp=time((time_t *) NULL);
91  content_info->version=ConstantString(GetWizardVersion(&version));
92  content_info->protocol_major=CipherProtocolMajor;
93  content_info->protocol_minor=CipherProtocolMinor;
94  content_info->signature=WizardSignature;
95  return(content_info);
96}
97
98/*
99%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100%                                                                             %
101%                                                                             %
102%                                                                             %
103%   D e s t r o y C o n t e n t I n f o                                       %
104%                                                                             %
105%                                                                             %
106%                                                                             %
107%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
108%
109%  DestroyContentInfo() zeros memory associated with the ContentInfo
110%  structure.
111%
112%  The format of the DestroyContentInfo method is:
113%
114%      ContentInfo *DestroyContentInfo(ContentInfo *content_info)
115%
116%  A description of each parameter follows:
117%
118%    o content_info: The content info.
119%
120*/
121WizardExport ContentInfo *DestroyContentInfo(ContentInfo *content_info)
122{
123  WizardAssert(CipherDomain,content_info != (ContentInfo *) NULL);
124  WizardAssert(CipherDomain,content_info->signature == WizardSignature);
125  (void) LogWizardEvent(TraceEvent,GetWizardModule(),"%s",
126    content_info->content);
127  if (content_info->random_info != (RandomInfo *) NULL)
128    content_info->random_info=DestroyRandomInfo(content_info->random_info);
129  if (content_info->hmac_info != (HMACInfo *) NULL)
130    content_info->hmac_info=DestroyHMACInfo(content_info->hmac_info);
131  if (content_info->entropy_info != (EntropyInfo *) NULL)
132    content_info->entropy_info=DestroyEntropyInfo(content_info->entropy_info);
133  if (content_info->passphrase != (char *) NULL)
134    content_info->passphrase=DestroyString(content_info->passphrase);
135  if (content_info->id != (char *) NULL)
136    content_info->id=DestroyString(content_info->id);
137  if (content_info->keyring != (char *) NULL)
138    content_info->keyring=DestroyString(content_info->keyring);
139  if (content_info->authenticate_info != (AuthenticateInfo *) NULL)
140    content_info->authenticate_info=DestroyAuthenticateInfo(
141      content_info->authenticate_info);
142  if (content_info->nonce != (char *) NULL)
143    content_info->nonce=DestroyString(content_info->nonce);
144  if (content_info->cipher_info != (CipherInfo *) NULL)
145    content_info->cipher_info=DestroyCipherInfo(content_info->cipher_info);
146  if (content_info->properties != (char *) NULL)
147    content_info->properties=DestroyString(content_info->properties);
148  if (content_info->content != (char *) NULL)
149    content_info->content=DestroyString(content_info->content);
150  if (content_info->version != (char *) NULL)
151    content_info->version=DestroyString(content_info->version);
152  if (content_info->plainblob != (BlobInfo *) NULL)
153    content_info->plainblob=DestroyBlob(content_info->plainblob);
154  if (content_info->cipherblob != (BlobInfo *) NULL)
155    content_info->cipherblob=DestroyBlob(content_info->cipherblob);
156  content_info->signature=(~WizardSignature);
157  content_info=(ContentInfo *) RelinquishWizardMemory(content_info);
158  return(content_info);
159}
160
161/*
162%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
163%                                                                             %
164%                                                                             %
165%                                                                             %
166%   G e t C o n t e n t I n f o                                               %
167%                                                                             %
168%                                                                             %
169%                                                                             %
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171%
172%  GetContentInfo() initializes the content info structure from the specified
173%  blob.
174%
175%  The format of the GetContentInfo method is:
176%
177%      WizardBooleanType GetContentInfo(ContentInfo *content_info,
178%        BLobInfo *cipher_blob,ExceptionInfo *exception)
179%
180%  A description of each parameter follows:
181%
182%    o content_info: the content info.
183%
184%    o cipher_blob: the cipher blob.
185%
186%    o exception: return any errors or warnings in this structure.
187%
188*/
189WizardExport WizardBooleanType GetContentInfo(ContentInfo *content_info,
190  BlobInfo *cipher_blob,ExceptionInfo *exception)
191{
192  char
193    *digest,
194    key[MaxTextExtent],
195    *options,
196    *target_digest;
197
198  const char
199    *content,
200    *tag;
201
202  HashInfo
203    *hash_info;
204
205  int
206    c;
207
208  long
209    type;
210
211  ssize_t
212    count;
213
214  size_t
215    bytes,
216    length;
217
218  StringInfo
219    *properties;
220
221  XMLTreeInfo
222    *child,
223    *node,
224    *rdf;
225
226  WizardAssert(CipherDomain,content_info != (ContentInfo *) NULL);
227  WizardAssert(CipherDomain,content_info->signature == WizardSignature);
228  WizardAssert(CipherDomain,cipher_blob != (BlobInfo *) NULL);
229  WizardAssert(CipherDomain,exception != (ExceptionInfo *) NULL);
230  (void) LogWizardEvent(TraceEvent,GetWizardModule(),"%s",
231    GetBlobFilename(cipher_blob));
232  (void) ResetWizardMemory(key,0,sizeof(key));
233  bytes=0;
234  digest=(char *) NULL;
235  for (c=ReadBlobByte(cipher_blob); (c != '>') && (c != EOF); )
236  {
237    length=MaxTextExtent;
238    options=AcquireString((char *) NULL);
239    while ((isgraph(c) != WizardFalse) && (c != '>') && (c != EOF))
240    {
241      register char
242        *p;
243
244      if (isalnum(c) == WizardFalse)
245        c=ReadBlobByte(cipher_blob);
246      else
247        {
248          /*
249            Get the key.
250          */
251          p=key;
252          do
253          {
254            if (isspace((int) ((unsigned char) c)) != 0)
255              break;
256            if (c == (int) '=')
257              break;
258            if ((size_t) (p-key) < MaxTextExtent)
259              *p++=(char) c;
260            c=ReadBlobByte(cipher_blob);
261          } while (c != EOF);
262          *p='\0';
263          p=options;
264          while (isspace((int) ((unsigned char) c)) != 0)
265            c=ReadBlobByte(cipher_blob);
266          if (c == (int) '=')
267            {
268              /*
269                Get the value.
270              */
271              c=ReadBlobByte(cipher_blob);
272              while ((c != (int) '?') && (c != EOF))
273              {
274                if ((size_t) (p-options+1) >= length)
275                  {
276                    *p='\0';
277                    length<<=1;
278                    options=(char *) ResizeQuantumMemory(options,length+
279                      MaxTextExtent,sizeof(*options));
280                    if (options == (char *) NULL)
281                      break;
282                    p=options+strlen(options);
283                  }
284                if (options == (char *) NULL)
285                  {
286                    (void) ThrowWizardException(exception,GetWizardModule(),
287                      ResourceError,"memory allocation failed: `%s'",
288                      GetBlobFilename(cipher_blob));
289                    return(WizardFalse);
290                  }
291                *p++=(char) c;
292                c=ReadBlobByte(cipher_blob);
293                if (*options != '?')
294                  if (isspace((int) ((unsigned char) c)) != 0)
295                    break;
296              }
297            }
298          *p='\0';
299          /*
300            Interpret key.
301          */
302          switch (*key)
303          {
304            case 'b':
305            case 'B':
306            {
307              if (LocaleCompare(key,"bytes") == 0)
308                {
309                  StripString(options);
310                  bytes=(size_t) atol(options);
311                  break;
312                }
313              break;
314            }
315            case 'd':
316            case 'D':
317            {
318              if (LocaleCompare(key,"digest") == 0)
319                {
320                  StripString(options);
321                  digest=AcquireString(options);
322                  break;
323                }
324              break;
325            }
326            default:
327              break;
328          }
329        }
330      while (isspace((int) ((unsigned char) c)) != 0)
331        c=ReadBlobByte(cipher_blob);
332    }
333    options=DestroyString(options);
334  }
335  if ((bytes == 0) || (digest == (char *) NULL))
336    {
337      (void) ThrowWizardException(exception,GetWizardModule(),AuthenticateError,
338        "authentication error: `%s'",GetBlobFilename(cipher_blob));
339      return(WizardFalse);
340    }
341  /*
342    Verify digest signature.
343  */
344  c=ReadBlobByte(cipher_blob);
345  properties=AcquireStringInfo(bytes);
346  count=ReadBlob(cipher_blob,bytes,GetStringInfoDatum(properties));
347  if (count != (ssize_t) bytes)
348    ThrowFileException(exception,FileError,GetBlobFilename(cipher_blob));
349  GetStringInfoDatum(properties)[bytes]='\0';
350  hash_info=AcquireHashInfo(SHA256Hash);
351  InitializeHash(hash_info);
352  UpdateHash(hash_info,properties);
353  FinalizeHash(hash_info);
354  target_digest=GetHashHexDigest(hash_info);
355  if (strcmp(target_digest,digest) != 0)
356    {
357      target_digest=DestroyString(target_digest);
358      digest=DestroyString(digest);
359      hash_info=DestroyHashInfo(hash_info);
360      properties=DestroyStringInfo(properties);
361      (void) ThrowWizardException(exception,GetWizardModule(),AuthenticateError,
362        "authentication error: `%s'",GetBlobFilename(cipher_blob));
363      return(WizardFalse);
364    }
365  hash_info=DestroyHashInfo(hash_info);
366  target_digest=DestroyString(target_digest);
367  digest=DestroyString(digest);
368  /*
369    Parse content XML.
370  */
371  rdf=NewXMLTree((const char *) GetStringInfoDatum(properties),exception);
372  properties=DestroyStringInfo(properties);
373  if (rdf == (XMLTreeInfo *) NULL)
374    return(WizardFalse);
375  child=GetXMLTreeChild(rdf,"cipher:Content");
376  if (child == (XMLTreeInfo *) NULL)
377    child=GetXMLTreeChild(rdf,"rdf:Description");
378  if (child == (XMLTreeInfo *) NULL)
379    {
380      rdf=DestroyXMLTree(rdf);
381      (void) ThrowWizardException(exception,GetWizardModule(),AuthenticateError,
382        "authentication error: `%s'",GetBlobFilename(cipher_blob));
383      return(WizardFalse);
384    }
385  node=GetXMLTreeChild(child,(const char *) NULL);
386  while (node != (XMLTreeInfo *) NULL)
387  {
388    tag=GetXMLTreeTag(node);
389    content=GetXMLTreeContent(node);
390    if (strncmp(tag,"cipher:",7) != 0)
391      continue;
392    tag+=7;
393    switch (*tag)
394    {
395      case 'a':
396      {
397        if (strcmp(tag,"authenticate") == 0)
398          {
399            long
400              method;
401
402            method=ParseWizardOption(WizardAuthenticateOptions,WizardFalse,
403              content);
404            if (method < 0)
405              (void) ThrowWizardException(exception,GetWizardModule(),
406                OptionError,"unrecognized authentication method: `%s'",content);
407            content_info->authenticate_method=(AuthenticateMethod) method;
408            break;
409          }
410        break;
411      }
412      case 'c':
413      {
414        if (strcmp(tag,"chunksize") == 0)
415          {
416            content_info->chunksize=(unsigned int) atol(content);
417            break;
418          }
419        if (strcmp(tag,"create-date") == 0)
420          {
421            if (*ParseWizardTime(content,&content_info->create_date) != '\0')
422              (void) ThrowWizardException(exception,GetWizardModule(),
423                OptionError,"unrecognized timestamp: `%s'",content);
424            break;
425          }
426        break;
427      }
428      case 'e':
429      {
430        if (strcmp(tag,"entropy") == 0)
431          {
432            type=ParseWizardOption(WizardEntropyOptions,WizardFalse,content);
433            if (type < 0)
434              (void) ThrowWizardException(exception,GetWizardModule(),
435                OptionError,"unrecognized entropy type: `%s'",content);
436            content_info->entropy=(EntropyType) type;
437            break;
438          }
439        break;
440      }
441      case 'h':
442      {
443        if (strcmp(tag,"hmac") == 0)
444          {
445            type=ParseWizardOption(WizardHashOptions,WizardFalse,content);
446            if (type < 0)
447              (void) ThrowWizardException(exception,GetWizardModule(),
448                OptionError,"unrecognized HMAC hash: `%s'",content);
449            content_info->hmac=(HashType) type;
450            break;
451          }
452        break;
453      }
454      case 'i':
455      {
456        if (strcmp(tag,"id") == 0)
457          {
458            content_info->id=ConstantString(content);
459            break;
460          }
461        break;
462      }
463      case 'k':
464      {
465        if (strcmp(tag,"key-hash") == 0)
466          {
467            type=ParseWizardOption(WizardHashOptions,WizardFalse,content);
468            if (type < 0)
469              (void) ThrowWizardException(exception,GetWizardModule(),
470                OptionError,"unrecognized key hash: `%s'",content);
471            content_info->key_hash=(HashType) type;
472            break;
473          }
474        if (strcmp(tag,"key-length") == 0)
475          {
476            content_info->key_length=(unsigned int) strtod(content,(char **)
477              NULL);
478            break;
479          }
480        break;
481      }
482      case 'l':
483      {
484        if (strcmp(tag,"level") == 0)
485          {
486            type=ParseWizardOption(WizardEntropyLevelOptions,WizardFalse,
487              content);
488            if (type < 0)
489              (void) ThrowWizardException(exception,GetWizardModule(),
490                OptionError,"unrecognized entropy level: `%s'",content);
491            content_info->level=(unsigned int) type;
492            break;
493          }
494        break;
495      }
496      case 'm':
497      {
498        if (strcmp(tag,"mode") == 0)
499          {
500            type=ParseWizardOption(WizardModeOptions,WizardFalse,content);
501            if (type < 0)
502              (void) ThrowWizardException(exception,GetWizardModule(),
503                OptionError,"unrecognized cipher mode: `%s'",content);
504            content_info->mode=(CipherMode) type;
505            break;
506          }
507        if (strcmp(tag,"modify-date") == 0)
508          {
509            if (*ParseWizardTime(content,&content_info->modify_date) != '\0')
510              (void) ThrowWizardException(exception,GetWizardModule(),
511                OptionError,"unrecognized timestamp: `%s'",content);
512            break;
513          }
514        break;
515      }
516      case 'n':
517      {
518        if (strcmp(tag,"nonce") == 0)
519          {
520            content_info->nonce=ConstantString(content);
521            break;
522          }
523        break;
524      }
525      case 'r':
526      {
527        if (strcmp(tag,"random-hash") == 0)
528          {
529            type=ParseWizardOption(WizardHashOptions,WizardFalse,content);
530            if (type < 0)
531              (void) ThrowWizardException(exception,GetWizardModule(),
532                OptionError,"unrecognized random hash: `%s'",content);
533            content_info->random_hash=(HashType) type;
534            break;
535          }
536        break;
537      }
538      case 's':
539      {
540        if (strcmp(tag,"session") == 0)  /* deprecated */
541          {
542            content_info->id=ConstantString(content);
543            break;
544          }
545        break;
546      }
547      case 't':
548      {
549        if (strcmp(tag,"timestamp") == 0)
550          {
551            if (*ParseWizardTime(content,&content_info->timestamp) != '\0')
552              (void) ThrowWizardException(exception,GetWizardModule(),
553                OptionError,"unrecognized timestamp: `%s'",content);
554            break;
555          }
556        if (strcmp(tag,"type") == 0)
557          {
558            type=ParseWizardOption(WizardCipherOptions,WizardFalse,content);
559            if (type < 0)
560              (void) ThrowWizardException(exception,GetWizardModule(),
561                OptionError,"unrecognized cipher type: `%s'",content);
562            content_info->cipher=(CipherType) type;
563            break;
564          }
565        break;
566      }
567      case 'v':
568      {
569        if (strcmp(tag,"version") == 0)
570          {
571            if (content_info->version != (char *) NULL)
572              content_info->version=DestroyString(content_info->version);
573            content_info->version=ConstantString(content);
574            break;
575          }
576        break;
577      }
578      default:
579        break;
580    }
581    node=GetXMLTreeSibling(node);
582  }
583  rdf=DestroyXMLTree(rdf);
584  /*
585    Consume trailer (i.e. <?cipherpacket?>).
586  */
587  while ((c != EOF) && (c != '>'))
588  {
589    c=ReadBlobByte(cipher_blob);
590    while (isspace((int) ((unsigned char) c)) != 0)
591      c=ReadBlobByte(cipher_blob);
592  }
593  c=ReadBlobByte(cipher_blob);
594  c=ReadBlobByte(cipher_blob);
595  return(WizardTrue);
596}
597
598/*
599%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
600%                                                                             %
601%                                                                             %
602%                                                                             %
603%   P r i n t C i p h e r P r o p e r t i e s                                 %
604%                                                                             %
605%                                                                             %
606%                                                                             %
607%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
608%
609%  PrintCipherProperties() prints properties associated with a cipher packet
610%  to the specified file.
611%
612%  The format of the PrintCipherProperties method is:
613%
614%      WizardBooleanType PrintCipherProperties(const CipherInfo *content_info,
615%        FILE *file)
616%
617%  A description of each parameter follows:
618%
619%    o content_info: The content info.
620%
621%    o file: the file, typically stdout.
622%
623*/
624WizardExport WizardBooleanType PrintCipherProperties(
625  const ContentInfo *content_info,FILE *file)
626{
627  char
628    chunksize[MaxTextExtent],
629    timestamp[MaxTextExtent];
630
631  (void) fprintf(file,"Filename: %s\n",content_info->content);
632  if (content_info->properties != (char *) NULL)
633    (void) fprintf(file,"  Properties: %s\n",content_info->properties);
634  (void) FormatWizardTime(content_info->modify_date,MaxTextExtent,timestamp);
635  (void) fprintf(file,"  Cipher:\n");
636  (void) fprintf(file,"    type: %s\n",WizardOptionToMnemonic(
637    WizardCipherOptions,content_info->cipher));
638  (void) fprintf(file,"    mode: %s\n",WizardOptionToMnemonic(WizardModeOptions,
639    content_info->mode));
640  (void) fprintf(file,"    nonce: %s\n",content_info->nonce);
641  (void) fprintf(file,"  Authenticate:\n");
642  (void) fprintf(file,"    method: %s\n",WizardOptionToMnemonic(
643    WizardAuthenticateOptions,content_info->authenticate_method));
644  (void) fprintf(file,"  Key:\n");
645  (void) fprintf(file,"    hash: %s\n",WizardOptionToMnemonic(WizardHashOptions,
646    content_info->key_hash));
647  (void) fprintf(file,"    length: %lu\n",1UL*content_info->key_length);
648  (void) fprintf(file,"    id: %s\n",content_info->id);
649  (void) fprintf(file,"  Entropy Generator:\n");
650  (void) fprintf(file,"    type: %s\n",WizardOptionToMnemonic(
651    WizardEntropyOptions,content_info->entropy));
652  (void) fprintf(file,"    level: %lu\n",1UL*content_info->level);
653  (void) FormatWizardSize(content_info->chunksize,chunksize);
654  (void) fprintf(file,"  Keyed-Hashed Message Authentication Code:\n");
655  (void) fprintf(file,"    hash: %s\n",WizardOptionToMnemonic(WizardHashOptions,
656    content_info->hmac));
657  (void) fprintf(file,"  Random Generator:\n");
658  (void) fprintf(file,"    hash: %s\n",WizardOptionToMnemonic(WizardHashOptions,
659    content_info->random_hash));
660  (void) fprintf(file,"  Chunksize: %s\n",chunksize);
661  (void) fprintf(file,"  Dates:\n");
662  (void) fprintf(file,"    modify: %s\n",timestamp);
663  (void) FormatWizardTime(content_info->create_date,MaxTextExtent,timestamp);
664  (void) fprintf(file,"    create: %s\n",timestamp);
665  (void) FormatWizardTime(content_info->timestamp,MaxTextExtent,timestamp);
666  (void) fprintf(file,"    timestamp: %s\n",timestamp);
667  (void) fprintf(file,"  Protocol: %u.%u\n",(unsigned int)
668    content_info->protocol_major,(unsigned int) content_info->protocol_minor);
669  if (content_info->version != (char *) NULL)
670    (void) fprintf(file,"  Version: %s\n",content_info->version);
671  return(ferror(file) != 0 ? WizardFalse : WizardTrue);
672}
Note: See TracBrowser for help on using the browser.