modules/mm/mm.c

/* [<][>]
[^][v][top][bottom][index][help] */

FUNCTIONS

This source file includes following functions.
  1. MM_store
  2. MM_get_msg_headers
  3. MM_extract_mime
  4. is_supported_MIMEtype
  5. dispatch_to_driver
  6. parse_text_plain
  7. parse_message_rfc822
  8. parse_multipart_alternative
  9. parse_multipart_signed
  10. status
  11. get_mail_hdr_field
  12. get_header_line
  13. write_file
  14. read_file
  15. put_in_file
  16. do_regex_test
  17. mm_searched
  18. mm_exists
  19. mm_expunged
  20. mm_flags
  21. mm_notify
  22. mm_list
  23. mm_lsub
  24. mm_status
  25. mm_log
  26. mm_dlog
  27. mm_login
  28. mm_critical
  29. mm_nocritical
  30. mm_diskerror
  31. mm_fatal

   1 /***************************************
   2   $Revision: 2.25 $
   3 
   4   mm - MIME Parser module. Functions to parse a mail message part,
   5   find if it is MIME-encapsulated, dispatch the part to the
   6   appropriate drivers (also included) and return tree nodes 
   7   with all MIME information.
   8 
   9   Status: COMPLETE, NOT REVUED, TESTED
  10 
  11   Design and implementation by: daniele@ripe.net
  12 
  13   ******************/ /******************
  14   Copyright (c) 2000                              RIPE NCC
  15  
  16   All Rights Reserved
  17   
  18   Permission to use, copy, modify, and distribute this software and its
  19   documentation for any purpose and without fee is hereby granted,
  20   provided that the above copyright notice appear in all copies and that
  21   both that copyright notice and this permission notice appear in
  22   supporting documentation, and that the name of the author not be
  23   used in advertising or publicity pertaining to distribution of the
  24   software without specific, written prior permission.
  25   
  26   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  27   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  28   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  29   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  30   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  31   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  32   ***************************************/
  33 
  34 /* Pieces of this code stolen and/or adapted from mtest.c, 
  35  * part of the IMAP toolkit by Mark Crispin:
  36  */
  37 
  38 /* Original version Copyright 1988 by The Leland Stanford Junior University
  39  * Copyright 1999 by the University of Washington
  40  *
  41  *  Permission to use, copy, modify, and distribute this software and its
  42  * documentation for any purpose and without fee is hereby granted, provided
  43  * that the above copyright notices appear in all copies and that both the
  44  * above copyright notices and this permission notice appear in supporting
  45  * documentation, and that the name of the University of Washington or The
  46  * Leland Stanford Junior University not be used in advertising or publicity
  47  * pertaining to distribution of the software without specific, written prior
  48  * permission.  This software is made available "as is", and
  49  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  50  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  51  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  52  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  53  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  54  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  55  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  56  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  57  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  58  *
  59  */
  60 
  61 
  62 
  63 /**************************
  64 
  65 
  66 "Every program attempts to expand until it can read mail.
  67  Those programs which cannot so expand are replaced by
  68  ones which can."
  69                 (Jamie Zawinski)
  70 
  71 
  72 **************************/
  73 
  74 
  75 
  76 /* Standard headers */
  77 #include <stdio.h>
  78 #include <signal.h>
  79 #include <string.h>
  80 #include <sys/time.h>
  81 #include <sys/types.h>
  82 /* #include <sys/param.h> */
  83 #include <netdb.h>
  84 #include <regex.h>
  85 #include <unistd.h>
  86 
  87 
  88 
  89 /* This is the local header */
  90 #include "mm.h"
  91 
  92 /* Other RIP headers */
  93 #include "erroutines.h"
  94 
  95 
  96 /* 
  97  * Globals to store shared data for tree nodes 
  98  * These variables come from EP 
  99  */
 100 
 101 extern char EP_outputPrefix[FILENAME_LENGTH];
 102 extern char EP_keyRing[FILENAME_LENGTH];
 103 extern int EP_TreeHeight;
 104 extern int  EP_Node_ID;
 105 
 106 /* Global variables to be used in this module */
 107 long debug = DEFAULT_DEBUG;
 108 
 109 char *supported_MIME_types[MAXSUPPTYPES] = {
 110   "UNKNOWN/UNKNOWN", "TEXT/PLAIN", "APPLICATION/PGP", "MULTIPART/SIGNED", 
 111   "MULTIPART/MIXED", "MULTIPART/ALTERNATIVE", "MULTIPART/DIGEST",
 112   "MESSAGE/RFC822"
 113 };
 114 
 115 long pass = 0;
 116 
 117 
 118 /* 
 119    FIXMEs:
 120    - Revise the whole debug system, debug messages etc. - right now
 121      an enormous and globally useless quantity of information is dumped.
 122 */
 123 
 124 
 125 /*+++++++++++++++++++++++++++++++++++++++
 126  
 127   API functions
 128  
 129   +++++++++++++++++++++++++++++++++++++++*/
 130 
 131 
 132 
 133 /*++++++++++
 134  *
 135  * MM_store(). Stores a file (or stdin) in another file,
 136  * "escaping" the lines starting with "From " by adding
 137  * a ">" sign. This is necessary because we need to deal
 138  * with files that are "unix mailboxes".
 139  *
 140  * This function puts a limit to the line size that a mail
 141  * message may have; officially, there is no limit to this size,
 142  * but we prefer to add this limit to avoid buffer overflow.
 143  * The line size limit is MAXBUFSIZE, defined in mm.h .
 144  *
 145  ++++++++++*/
 146 
 147 
 148 int MM_store (char *source_file, char *destination_file, long custom_debug)
     /* [<][>][^][v][top][bottom][index][help] */
 149 {
 150 
 151 
 152 /* The regexp we will be looking for */
 153 #define REGEXP "^From "
 154 
 155 
 156   char line[MAXBUFSIZE];
 157   FILE *ifp;
 158   FILE *ofp;
 159   FILE *actualfile; /* Actual "file" to be opened (can be stdin) */
 160   char *tmpstr;
 161   short using_file = 0;
 162   long numlines = 0;
 163   time_t ti = time (0);
 164 
 165 
 166 
 167   if (custom_debug)
 168     debug = custom_debug;
 169 
 170   /* Check if we need to parse a file or stdin.
 171    * We parse stdin if source_file is "-" . 
 172    */
 173 
 174   if (strcmp(source_file,"-"))
 175     {
 176       if ((ifp = fopen(source_file,"r")) != NULL)
 177         {
 178           ER_dbg_va (FAC_MM, ASP_MM_GEN, "MM_store: input file %s",source_file);
 179           actualfile = ifp;
 180           using_file = 1;
 181         }
 182       else
 183         {
 184           /* XXX Use perror to state reason? */
 185           ER_perror(FAC_MM, MM_CANTOPEN, "%s for reading", source_file);
 186           die;
 187         }
 188     }
 189   else
 190     {
 191       ER_dbg_va (FAC_MM, ASP_MM_GEN, "MM_store: input from stdin");
 192       actualfile = stdin;
 193     }
 194 
 195   if ((ofp = fopen(destination_file,"w")) != NULL)
 196     {
 197       while ((tmpstr = fgets(line, MAXBUFSIZE, actualfile)) != NULL)
 198         {
 199           numlines++;
 200           if (strlen(line) >= MAXBUFSIZE - 1)
 201             {
 202               ER_inf_va(FAC_MM, ASP_MM_SEC, "Line too long error. Possible buffer overflow attempt.");
 203               ER_perror(FAC_MM, MM_LINETOOLONG, "%ld",numlines);
 204               /* XXX Should be handled better - report line too long to caller,
 205                  so that a failed ack can be sent */
 206               die;
 207             }
 208           if (numlines == 1)
 209             {
 210               /* If the first line is not a "^From " line, put a fake one */
 211               if (!do_regex_test(REGEXP,(char *)line))
 212                   fprintf (ofp,"From dbase@whois.ripe.net %s",ctime (&ti));
 213               fputs (line,ofp);
 214             }
 215           else
 216             {
 217               if (do_regex_test(REGEXP,(char *)line)) fprintf (ofp,">");
 218               fputs (line,ofp);
 219             }
 220         }
 221       fclose(ofp);
 222       if (using_file) fclose(ifp);
 223       return(0);
 224     }
 225   else
 226     {
 227       /* XXX Use perror to state reason? */
 228       ER_perror(FAC_MM, MM_CANTOPEN, "%s for writing", destination_file);
 229       die;
 230     }
 231 
 232   /* Even though we should never get here... */
 233   return(0);
 234 
 235 } /* MM_store() */
 236 
 237 
 238 
 239 /**********
 240  *
 241  * MM_get_msg_headers(). Get the headers of a mail contained in a file.
 242  *
 243  **********/
 244 
 245 int MM_get_msg_headers(
     /* [<][>][^][v][top][bottom][index][help] */
 246                    const char *mail_file,                       /* Input mail file */
 247                    EP_Mail_Descr *mail_descr,           /* Structure containing the headers */
 248                    long mesgno,                         /* msg number in the input file */
 249                    long custom_debug                    /* debug level */
 250                    )
 251 {
 252   MAILSTREAM *stream = NULL;            /* MAILSTREAM is defined in c-client */
 253   char tmp[MAILTMPLEN];                 /* MAILTMPLEN is set in c-client */
 254   int retcode;                          /* return code of the subroutine */
 255   STRINGLIST *lines;                    /* STRINGLIST is defined in c-client */
 256   STRINGLIST *cur;
 257   BODY *body;
 258 
 259 #include "linkage.c"            /* c-client requires it to be included... */
 260 
 261 
 262   /* If the supplied debug level is not null, then the global debug level
 263    * takes that value 
 264    */
 265   if (custom_debug)
 266     debug = custom_debug;
 267 
 268 
 269   /* open mailbox and get the mail stream */
 270   sprintf (tmp, "%s", mail_file);
 271   stream = mail_open (stream,tmp,debug ? OP_DEBUG : NIL);
 272 
 273   /* Process the stream */
 274   if (!stream)
 275     {
 276       ER_perror(FAC_MM, MM_INVMBX, "%s", mail_file);
 277       die;
 278     }
 279   else
 280     {
 281       ER_inf_va (FAC_MM, ASP_MM_GEN, "Getting message headers.");
 282       ER_dbg_va (FAC_MM, ASP_MM_GEN, "Message status:");
 283       status (stream);                  /* report message status */
 284       ER_dbg_va (FAC_MM, ASP_MM_GEN, "End of message status.");
 285       
 286       /* Get the headers */
 287 
 288       lines = mail_newstringlist ();      
 289       cur = lines;
 290       
 291       /* Get information about the mentioned lines in the header */
 292 
 293 
 294       mail_descr->from = get_mail_hdr_field(stream, mesgno, cur, "From");
 295 
 296       mail_descr->subject = get_mail_hdr_field(stream, mesgno, cur, "Subject");
 297 
 298       mail_descr->date = get_mail_hdr_field(stream, mesgno, cur, "Date");
 299       
 300       mail_descr->message_id = get_mail_hdr_field(stream, mesgno, cur, "Message-Id");
 301       
 302       mail_descr->reply_to = get_mail_hdr_field(stream, mesgno, cur, "Reply-To");
 303       
 304       mail_descr->cc = get_mail_hdr_field(stream, mesgno, cur, "Cc");
 305       
 306 
 307 
 308       mail_descr->content_type = (Mail_Header_Field *)UT_malloc(sizeof(Mail_Header_Field));
 309       /* This gets all the line (with encoding etc.) */
 310       /* mail_descr->content_type = get_mail_hdr_field(stream,mesgno,cur,"Content-Type"); */
 311 
 312       /* This only gets the content-type itself in canonized form: */
 313       mail_fetchstructure(stream,mesgno,&body);
 314       if (body)
 315         {
 316           mail_descr->content_type->field = (char *)UT_malloc(STR_M);
 317           mail_descr->content_type->next = NULL;
 318           sprintf(mail_descr->content_type->field,"%s",body_types[body->type]);
 319           if (body->subtype)
 320             sprintf(mail_descr->content_type->field+strlen(mail_descr->content_type->field),"/%s",body->subtype);
 321           sprintf(mail_descr->content_type->field+strlen(mail_descr->content_type->field),"\n\n");
 322         }
 323       
 324       mail_free_stringlist (&lines);
 325       
 326       ER_inf_va (FAC_MM, ASP_MM_GEN, "Got message headers.");
 327       
 328 
 329 
 330       mail_close(stream);
 331 
 332       retcode = 0;
 333 
 334       
 335     }
 336 
 337 
 338   return(retcode);
 339 
 340 } /* MM_get_msg_headers() */
 341 
 342 
 343 
 344 /* 
 345  * MM_extract_mime(): extract MIME information
 346  * This function was inspired by display_body() in mtest.c,
 347  * in the IMAP distribution. It has been largely re-engineered
 348  * to support MIME, and be modular.
 349  * It now only acts as an initializer of the mail stream,
 350  * sending then the stream to be dispatched to the appropriate
 351  * MIME drivers.
 352  */
 353 
 354 
 355 
 356 
 357 int MM_extract_mime (
     /* [<][>][^][v][top][bottom][index][help] */
 358                      const char *sourcefile,                    /* Input file containing the mail */
 359                      char *pfx,                         /* "prefix": this can be NULL at the
 360                                                          * first call of the function */
 361                      EP_mail_node *mailnode,            /* initialized node where to stock info */
 362                      long custom_debug                  /* debug level */
 363                      )
 364 {
 365 
 366   MAILSTREAM *stream = NULL;            /* MAILSTREAM is defined in c-client */
 367   BODY *body;                           /* BODY is defined in c-client */
 368   char tmp[MAILTMPLEN];                 /* MAILTMPLEN is set in c-client */
 369   int retcode = 0;                      /* return code of the subroutine */
 370   long mesgno = 1;
 371 
 372 
 373 #include "linkage.c"            /* c-client requires it to be included... */
 374 
 375   /* 
 376    * This (global) variable counts the number of times we pass through
 377    * MM_extract_mime().
 378    * It is useful in generating unique temporary files (see below).
 379    */
 380 
 381   pass++;
 382   ER_inf_va (FAC_MM, ASP_MM_GEN, "MM_extract_mime, pass %ld",pass);
 383 
 384   if (custom_debug)
 385     debug = custom_debug;
 386 
 387   /* ER_dbg_va (FAC_MM, ASP_MM_GEN, " EP_outputPrefix: %s",EP_outputPrefix);
 388      ER_dbg_va (FAC_MM, ASP_MM_GEN, " EP_keyRing: %s",EP_keyRing); */
 389 
 390 
 391   /* open file and get the mail stream from there*/
 392 
 393   sprintf (tmp, "%s", sourcefile);
 394   
 395   stream = mail_open (stream,tmp,debug ? OP_DEBUG : NIL);
 396 
 397   /* Process the stream */
 398   if (!stream)
 399     {
 400       ER_perror(FAC_MM, MM_INVMBX, "%s", sourcefile);
 401       die;
 402     }
 403   else
 404     {
 405       if (debug >=2)
 406         {
 407           ER_inf_va (FAC_MM, ASP_MM_GEN, "Getting message headers.");
 408           ER_dbg_va (FAC_MM, ASP_MM_GEN, "Message status:");
 409           status (stream);                      /* report message status */
 410           ER_dbg_va (FAC_MM, ASP_MM_GEN, "End of message status.");
 411         }
 412       if (debug >= 2) 
 413         ER_dbg_va (FAC_MM, ASP_MM_GEN, "Calling mail_fetchstructure...");
 414       mail_fetchstructure (stream,mesgno,&body);
 415 
 416       if (body)
 417         {
 418           ER_dbg_va (FAC_MM, ASP_MM_GEN, "Got body, dispatching to drivers...");
 419           dispatch_to_driver(stream, body, pfx, mailnode);
 420         }
 421       
 422     }
 423 
 424   ER_dbg_va (FAC_MM, ASP_MM_GEN, "Closing the stream %s...",stream->mailbox);
 425   mail_close(stream);
 426   ER_dbg_va (FAC_MM, ASP_MM_GEN, "Stream Closed.");
 427   
 428 
 429   return(retcode);
 430 
 431 } /* MM_extract_mime() */
 432 
 433 
 434 
 435 /*********************************************/
 436 
 437 /***************************************
 438  *
 439  * End of API functions
 440  *
 441  ***************************************/
 442 
 443 
 444 
 445 /* Internal functions */
 446 
 447 t_MM_type is_supported_MIMEtype (BODY *body)
     /* [<][>][^][v][top][bottom][index][help] */
 448 {
 449 
 450   char *mimetype_string;
 451   char tmpstr[STR_S];
 452   char *tmptype = tmpstr;
 453   int i;
 454   t_MM_type mtypecode = 0;
 455 
 456   
 457   /* mimetype_string is the MIME type of the message */
 458   mimetype_string = (char *)UT_malloc(STR_S);
 459   sprintf (mimetype_string,"%s",body_types[body->type]);
 460   if (body->subtype)
 461     sprintf (mimetype_string + strlen(mimetype_string),"/%s",body->subtype);
 462 
 463   /* 
 464    * We cycle to compare the MIME type of the message
 465    * to each of the MIME types we support
 466    */
 467   i = 0;
 468   tmptype = supported_MIME_types[i];
 469 
 470   while ((i < MAXSUPPTYPES) && (tmptype))
 471     {
 472       if (!strcmp(tmptype,mimetype_string))
 473         {
 474           mtypecode = i;
 475           break;
 476         }
 477       tmptype = supported_MIME_types[++i];
 478     }
 479 
 480   free(mimetype_string);
 481 
 482   return(mtypecode);
 483 
 484 } /* is_supported_MIMEtype() */
 485 
 486 
 487 
 488 /****
 489  *
 490  * dispatch_to_driver()
 491  * This function dispatches a message to the proper driver
 492  * which will parse it, following the MIME type
 493  *
 494  ****/
 495 
 496 void dispatch_to_driver(MAILSTREAM *stream, BODY *body, char *pfx, EP_mail_node *mailnode)
     /* [<][>][^][v][top][bottom][index][help] */
 497 {
 498 
 499   t_MM_type is_supported;
 500 
 501   is_supported = is_supported_MIMEtype(body);
 502   /* We assign the given MIME Type to the node */
 503   mailnode->MIMEContentType = is_supported;
 504 
 505 
 506   ER_dbg_va (FAC_MM, ASP_MM_GEN, " mailnode->MIMEContentType: %s",supported_MIME_types[mailnode->MIMEContentType]);
 507 
 508   if (!strcmp(supported_MIME_types[is_supported],"TEXT/PLAIN"))
 509     {
 510       parse_text_plain(stream, body, pfx, mailnode);
 511     }
 512   else if (!strcmp(supported_MIME_types[is_supported],"APPLICATION/PGP"))
 513     {
 514       parse_application_pgp(stream, body, pfx, mailnode);
 515     }
 516   else if (!strcmp(supported_MIME_types[is_supported],"MULTIPART/ALTERNATIVE"))
 517     {
 518       parse_multipart_alternative(stream, body, pfx, mailnode);
 519     }
 520   else if (!strcmp(supported_MIME_types[is_supported],"MULTIPART/MIXED"))
 521     {
 522       parse_multipart_mixed(stream, body, pfx, mailnode);
 523     }
 524   else if (!strcmp(supported_MIME_types[is_supported],"MULTIPART/SIGNED"))
 525     {
 526       parse_multipart_signed(stream, body, pfx, mailnode);
 527     }
 528   else if (!strcmp(supported_MIME_types[is_supported],"MULTIPART/DIGEST"))
 529     {
 530       parse_multipart_digest(stream, body, pfx, mailnode);
 531     }
 532   else if (!strcmp(supported_MIME_types[is_supported],"MESSAGE/RFC822"))
 533     {
 534       parse_message_rfc822(stream, body, pfx, mailnode);
 535     }
 536   else
 537     {
 538       /* It's not a supported MIMEtype... */
 539       parse_unknown_unknown(stream, body, pfx, mailnode);
 540     }
 541 
 542 } /* dispatch_to_driver() */
 543 
 544 
 545 void parse_text_plain(MAILSTREAM *stream, BODY *body, char *pfx, EP_mail_node *mailnode)
     /* [<][>][^][v][top][bottom][index][help] */
 546 {
 547 
 548   char tmp[MAILTMPLEN];
 549   char *mailtext;
 550 
 551   
 552   if (debug >= 2)
 553     ER_dbg_va (FAC_MM, ASP_MM_GEN, " Lines: %lu",body->size.lines);
 554 
 555   if (pfx == NULL)                      /* If top level, is not inside a multipart */
 556     {
 557       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "MAILNODE->FILE: %s",mailnode->file);
 558       /* The filename of the root node has to be redefined to the processed file */
 559       /* remove(mailnode->file); */ /* This causes complaints by mail_close() */
 560       free(mailnode->file);
 561       mailnode->file = (char *)UT_malloc(FILENAME_LENGTH);
 562       sprintf(mailnode->file,"%s%d",EP_outputPrefix,mailnode->nodeID);
 563       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "MAILNODE->FILE: %s",mailnode->file);
 564     }
 565   else
 566 
 567   ER_dbg_va (FAC_MM, ASP_MM_GEN, " mailnode->file: %s",mailnode->file);
 568 
 569   /* Get the plain text contents of the message */
 570   mailtext = tmp;
 571   mailtext = mail_fetchtext(stream, 1);
 572 
 573   if (debug >= 2)
 574     {
 575       ER_dbg_va (FAC_MM, ASP_MM_GEN, "Message contents:");
 576       ER_dbg_va (FAC_MM, ASP_MM_GEN, "\n\n%s\n",mailtext); 
 577     }
 578       
 579 
 580   /* Place the results in the file pointed by the node*/
 581   write_file(mailnode->file,mailtext,strlen(mailtext));
 582 
 583   PA_ParseMessage(mailnode);
 584 
 585   /* if (debug) printf ("mailnode->nodeID: %d\n",mailnode->nodeID); */
 586   /* if (debug) printf ("mailnode->MIMEContentType: %d\n",mailnode->MIMEContentType); */
 587 
 588 }
 589 
 590 void parse_message_rfc822(MAILSTREAM *stream, BODY *body, char *pfx, EP_mail_node *mailnode)
     /* [<][>][^][v][top][bottom][index][help] */
 591 {
 592 
 593   /* The idea here is to strip the message/rfc822 part from its mail headers,
 594    * and store it in a file to resend to MM_extract_mime().
 595    */
 596 
 597   char tmp[MAILTMPLEN];
 598   char *mailtext;
 599   char *content;
 600   char tmpfile[FILENAMELEN];
 601   time_t ti = time (0);
 602   
 603   
 604   if (pfx == NULL)                      /* If top level, is not inside a multipart */
 605     {
 606       /* pfx = (char *)UT_malloc(STR_L);
 607       pfx = "";         */      /* Dummy prefix */
 608       /* The filename of the root node has to be redefined to the processed file */
 609       mailnode->file = (char *)UT_malloc(FILENAME_LENGTH);
 610       sprintf(mailnode->file,"%s%d",EP_outputPrefix,mailnode->nodeID);
 611       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "MAILNODE->FILE: %s",mailnode->file);
 612     }
 613 
 614   /* Get the plain text contents of the message */
 615   mailtext = tmp;
 616   mailtext = mail_fetchtext(stream, 1);
 617 
 618 
 619   /* This buffer has to be dumped in a file from where it will be read by MM_extract_mime():
 620    * another stream will be opened, so the format of the file must be correct.
 621    * The LINELENGTH is to take the first 2 lines into account.
 622    */
 623 
 624   content = (char *)UT_malloc(2*LINELENGTH + strlen(mailtext) + 2);
 625   sprintf (content,"From dbase@whois.ripe.net %s",ctime (&ti));
 626   sprintf (content+strlen(content), "%s\n", mailtext);
 627      
 628 
 629   /* Generation of a temporary file:
 630    * The file must be unique inside the process. If we rewrite
 631    * on the same tmp file, which is used as a mailbox by c-client,
 632    * the c-client library has problems because it sees the mailbox changes
 633    * (I had problems with multipart/digest and message/rfc822).
 634    * "pass" is a global variable which increases every time we pass
 635    * through MM_extract_mime(): it should hence be unique each time
 636    * we call a driver.
 637    */
 638   sprintf (tmpfile,"%s.tmp.%ld",mailnode->file,pass);
 639   write_file(tmpfile,content,strlen(content));
 640 
 641   MM_extract_mime(tmpfile, pfx, mailnode, debug);
 642 
 643   /* Clean up... */
 644   free(content);
 645   remove(tmpfile);
 646 
 647 }
 648 
 649 void parse_multipart_alternative (MAILSTREAM *stream, BODY *body, char *pfx, EP_mail_node *mailnode)
     /* [<][>][^][v][top][bottom][index][help] */
 650 {
 651 
 652   char tmppfx[MAILTMPLEN];
 653   char childpfx[MAILTMPLEN];
 654   char tmppart[MAILTMPLEN];
 655   /*   char *s = tmppfx; */
 656   EP_mail_node *newnode;
 657   EP_mail_node *parsednode;
 658   EP_mail_node *nextnode;
 659   long i;
 660   PART *part;
 661   char *result;
 662   char *content;
 663   unsigned long length;
 664   time_t ti = time (0);
 665   char tmpfile[FILENAMELEN];
 666   char nodefile[FILENAMELEN];
 667   
 668 
 669   if (debug >= 2)
 670     ER_dbg_va (FAC_MM, ASP_MM_GEN, "Bytes: %lu",body->size.bytes);
 671 
 672   /* if not first time, extend prefix */
 673   if (pfx == NULL) 
 674     {
 675       tmppfx[0] = '\0';
 676       pfx = tmppfx;
 677     }
 678 
 679 
 680   /* Initialize the first node: it is an inner node */
 681 
 682   /* The tree height increases */
 683   EP_TreeHeight++;
 684 
 685   /* The number of nodes increases */
 686   EP_Node_ID++;
 687 
 688   sprintf (nodefile,"%s%d",EP_outputPrefix,EP_Node_ID);
 689   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "inner-nodefile: %s",nodefile);
 690 
 691   newnode = EP_InitializeNode(nodefile, EP_Node_ID);
 692   mailnode->inner = newnode;
 693 
 694   for (i = 1,part = body->nested.part; part; part = part->next)
 695     {
 696       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "i: %ld, pfx: %s",i,pfx);
 697       if (debug >= 3) ER_dbg_va (FAC_MM, ASP_MM_GEN, "MYDEBUG: pfx=%s, tmppfx=%s,",pfx,tmppfx);
 698       sprintf (tmppart,"%ld",i);
 699       result = mail_fetch_mime(stream, (long)1, tmppart, &length, (long)0);
 700       if (debug >= 3)
 701         {
 702           ER_dbg_va (FAC_MM, ASP_MM_GEN, "body->size.bytes: %lu",body->size.bytes);
 703           ER_dbg_va (FAC_MM, ASP_MM_GEN, "(&part->body)->size.bytes: %lu",(&part->body)->size.bytes);
 704           ER_dbg_va (FAC_MM, ASP_MM_GEN, "length: %lu",length);
 705         }
 706       
 707 
 708       /* This buffer has to be dumped in a file from where it will be read by MM_extract_mime():
 709        * another stream will be opened, so the format of the file must be correct.
 710        * The LINELENGTH is to take the first 2 lines into account 
 711        */
 712       content = (char *)UT_malloc(2*LINELENGTH + length + (&part->body)->size.bytes + 2);
 713       sprintf (content,"From dbase@whois.ripe.net %sMIME-Version: 1.0\n",ctime (&ti));
 714       /* snprintf (content+strlen(content), (size_t)(length + (&part->body)->size.bytes) + 2, "%s\n", result); */
 715       g_snprintf ((gchar *)(content+strlen(content)), (gulong)(length + (&part->body)->size.bytes) + 2, "%s\n", result);
 716       
 717       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "Result: \n---\n%s\n---\nEnd results.\n", content);
 718 
 719       /* Generation of a temporary file:
 720        * The file must be unique inside the process. If we rewrite
 721        * on the same tmp file, which is used as a mailbox by c-client,
 722        * the c-client library has problems because it sees it changes
 723        * (I had problems with multipart/digest and message/rfc822).
 724        * "pass" is a global variable which increases every time we pass
 725        * through MM_extract_mime(): it should hence be unique each time
 726        * we call a driver.
 727        */
 728       sprintf (tmpfile,"%s.tmp.%ld",newnode->file,pass);
 729       write_file(tmpfile,content,strlen(content));
 730 
 731       /* This is needed to extend the prefix */
 732       sprintf (childpfx,"%s%ld.",pfx,i);
 733       if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "childpfx: %s",childpfx);
 734       MM_extract_mime(tmpfile, childpfx, newnode, debug);
 735 
 736       /* Clean up... */
 737       free(content);
 738       remove(tmpfile);
 739 
 740       /* Initialize the next node (if it exists) */
 741 
 742       if (part->next != NULL)
 743         {
 744           EP_Node_ID++;
 745           sprintf (nodefile,"%s%d",EP_outputPrefix,EP_Node_ID);
 746           if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "next-nodefile: %s",nodefile);
 747           nextnode = EP_InitializeNode(nodefile, EP_Node_ID);
 748           parsednode = newnode;
 749           newnode = nextnode;
 750           parsednode->next = newnode;
 751         }
 752 
 753       i++;
 754       
 755     }
 756   
 757 } /* parse_multipart_alternative() */
 758 
 759 
 760 void parse_multipart_signed (MAILSTREAM *stream, BODY *body, char *pfx, EP_mail_node *mailnode)
     /* [<][>][^][v][top][bottom][index][help] */
 761 {
 762 
 763   char tmppfx[MAILTMPLEN];
 764   char tmppart[MAILTMPLEN];
 765   EP_mail_node *newnode;
 766   PART *part;
 767   char *result;
 768   char *content;
 769   unsigned long length;
 770   char tmpfile[FILENAMELEN];
 771   char nodefile[FILENAMELEN];
 772   struct VerifySignObject vSO;
 773   /*  int retcode; */
 774 
 775   if (debug >= 2)
 776     ER_dbg_va (FAC_MM, ASP_MM_GEN, "Bytes: %lu",body->size.bytes);
 777 
 778 
 779   /* if not first time, extend prefix */
 780   if (pfx == NULL) 
 781     {
 782       tmppfx[0] = '\0';
 783       pfx = tmppfx;
 784     }
 785 
 786 
 787   /* Initialize the inner node */
 788 
 789   /* The tree height increases */
 790   EP_TreeHeight++;
 791 
 792   /* The number of nodes increases */
 793   EP_Node_ID++;
 794 
 795   sprintf (nodefile,"%s%d",EP_outputPrefix,EP_Node_ID);
 796   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "inner-nodefile: %s",nodefile);
 797 
 798   newnode = EP_InitializeNode(nodefile, EP_Node_ID);
 799   mailnode->inner = newnode;
 800 
 801   /* We give the same content-type to the child so as not to leave the default
 802      value (-1) */
 803   newnode->MIMEContentType = mailnode->MIMEContentType;
 804 
 805   /* We must get the two parts of the message. The signed part
 806    * and the signature. There can't be more than two parts
 807    * (see RFC2015).
 808    */
 809 
 810   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "pfx: %s",pfx);
 811 
 812   /* Signed part: it is the first part of the message. */
 813 
 814   part = body->nested.part;
 815 
 816   sprintf (tmppart,"%s1",tmppfx);
 817   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "tmppart: %s",tmppart);
 818 
 819   result = mail_fetch_mime(stream, (long)1, tmppart, &length, (long)0);
 820   if (debug >= 3)
 821     {
 822       ER_dbg_va (FAC_MM, ASP_MM_GEN, "body->size.bytes: %lu",body->size.bytes);
 823       ER_dbg_va (FAC_MM, ASP_MM_GEN, "(&part->body)->size.bytes: %lu",(&part->body)->size.bytes);
 824       ER_dbg_va (FAC_MM, ASP_MM_GEN, "length: %lu",length);
 825     }
 826 
 827   /* The signed part must be dumped in a file together with the MIME headers */
 828 
 829   content = (char *)UT_malloc(length + (&part->body)->size.bytes + 2);
 830   snprintf (content,(size_t)(length + (&part->body)->size.bytes) + 2, "%s\n", result);
 831 
 832   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "Result: \n---\n%s\n---\nEnd results.\n", content);
 833 
 834   if (debug) ER_dbg_va (FAC_MM, ASP_MM_GEN, "MSG file: %s",newnode->file);
 835   write_file(newnode->file,content,strlen(content));
 836 
 837 
 838   free(content);
 839 
 840   /* Signature */
 841 
 842   part = part->next;
 843   sprintf (tmppart,"%s2",tmppfx);
 844   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "tmppart: %s",tmppart);
 845   
 846   result = mail_fetch_mime(stream, (long)1, tmppart, &length, (long)0);
 847   if (debug >= 2)
 848     {
 849       ER_dbg_va (FAC_MM, ASP_MM_GEN, "body->size.bytes: %lu\n",body->size.bytes);
 850       ER_dbg_va (FAC_MM, ASP_MM_GEN, "(&part->body)->size.bytes: %lu\n",(&part->body)->size.bytes);
 851       ER_dbg_va (FAC_MM, ASP_MM_GEN, "length: %lu\n",length);
 852     }
 853 
 854   /* The signature must be dumped _without_ MIME headers instead! 
 855    * Check where is the "length" variable... 
 856    */
 857 
 858   content = (char *)UT_malloc((&part->body)->size.bytes + 2);
 859 
 860   snprintf (content,(size_t)((&part->body)->size.bytes) + 2, "%s\n", result + length);
 861 
 862   if (debug >= 2) ER_dbg_va (FAC_MM, ASP_MM_GEN, "Result: \n---\n%s\n---\nEnd results.\n", content);
 863 
 864   sprintf (tmpfile,"%s.sig",newnode->file);
 865   if (debug) ER_dbg_va (FAC_MM, ASP_MM_GEN, "SIG file: %s",tmpfile);
 866   write_file(tmpfile,content,strlen(content));
 867 
 868   /* Calling the verification procedure */
 869 
 870   strcpy(vSO.iDocSigFilename, newnode->file);
 871   strcpy(vSO.iSigFilename, tmpfile);
 872   strcpy(vSO.keyRing, EP_keyRing);
 873   
 874   PA_VerifySignature(&vSO);
 875 
 876   newnode->isValidPGPSignature = vSO.isValid;
 877   newnode->keyID= vSO.keyID;
 878 
 879   EP_MIMEParse(newnode);
 880 
 881   free(content);
 882   remove(tmpfile);
 883 
 884 
 885 } /* parse_multipart_signed */
 886 
 887 
 888 
 889 /* MM status report
 890  * Accepts: MAIL stream
 891  */
 892 
 893 void status (MAILSTREAM *stream)
     /* [<][>][^][v][top][bottom][index][help] */
 894 {
 895   long i;
 896   char date[MAILTMPLEN];
 897   rfc822_date (date);
 898   ER_dbg_va (FAC_MM, ASP_MM_GEN, "%s",date);
 899   if (stream) 
 900     {
 901       if (stream->mailbox)
 902         {
 903           ER_dbg_va (FAC_MM, ASP_MM_GEN, " %s mailbox: %s",
 904                      stream->dtb->name,stream->mailbox);
 905           ER_dbg_va (FAC_MM, ASP_MM_GEN, " %lu messages, %lu recent",
 906                      stream->nmsgs,stream->recent);
 907         }
 908       else  ER_dbg_va (FAC_MM, ASP_MM_GEN, "% No mailbox is open on this stream");
 909       if (stream->user_flags[0]) 
 910         {
 911            ER_dbg_va (FAC_MM, ASP_MM_GEN, "Keywords: %s",stream->user_flags[0]);
 912           for (i = 1; i < NUSERFLAGS && stream->user_flags[i]; ++i)
 913            ER_dbg_va (FAC_MM, ASP_MM_GEN,", %s",stream->user_flags[i]);
 914           /* puts (""); */
 915         }
 916     }
 917 } /* status() */
 918 
 919 
 920 Mail_Header_Field *get_mail_hdr_field (MAILSTREAM *stream, 
     /* [<][>][^][v][top][bottom][index][help] */
 921                                        long mesgno, 
 922                                        STRINGLIST *cur, 
 923                                        const char *hdr_title)
 924 {
 925 
 926   char *tmphdr;
 927   char tmpline[MAXBUFSIZE];
 928   int i, j, c, tmpsize, titlesize;
 929   Mail_Header_Field *hdr_field;
 930   Mail_Header_Field *mhfp;
 931   Mail_Header_Field *newmhfp;
 932 
 933   mhfp = hdr_field = newmhfp = NULL;
 934 
 935   tmphdr = get_header_line(stream,mesgno,cur,hdr_title);
 936 
 937   tmpsize = strlen(tmphdr);
 938 
 939   /* Length of the header title plus ": "*/
 940   titlesize = strlen(hdr_title) + 2;
 941 
 942   j = 0;
 943 
 944   /* Get one line at a time, and put the header lines in the Mail Header Field */
 945 
 946   for (i = 0; i < tmpsize; i++)
 947     {
 948       c = tmphdr[i];
 949       if (c == 10) /* EOL */
 950         {
 951           if ((j > 1) || ((mhfp == NULL) && (i == tmpsize - 1))) /* j>1 and not j>0 because "\r" is always read;
 952                                                                   * The second option is needed for
 953                                                                   * the empty headers */
 954             {
 955               newmhfp = (Mail_Header_Field *)UT_malloc(sizeof(Mail_Header_Field));
 956               newmhfp->next = NULL;
 957               newmhfp->field = (char *)UT_malloc(j + 2);
 958               if (j > 1)
 959                 /* We do not copy here the header title */
 960                 sprintf (newmhfp->field,"%s\n",tmpline + titlesize);
 961               else
 962                 sprintf (newmhfp->field,"\n");
 963 
 964 
 965               if (mhfp == NULL)
 966                 {
 967                   mhfp = newmhfp;
 968                   hdr_field = newmhfp;
 969                 }
 970               else
 971                 {
 972                   mhfp->next = newmhfp;
 973                   mhfp = newmhfp;
 974                 }
 975             }
 976           j = 0;          
 977         }
 978       else
 979         {
 980           sprintf (tmpline + j++,"%c", c);
 981         }
 982 
 983     }
 984 
 985   free(tmphdr);
 986 
 987   return (hdr_field);
 988 
 989 } /* get_mail_hdr_field() */
 990 
 991 
 992 
 993 char *get_header_line (MAILSTREAM *stream, long mesgno, STRINGLIST *cur, const char *hdr_title)
     /* [<][>][^][v][top][bottom][index][help] */
 994 {
 995 
 996   unsigned long offset;
 997   size_t tmplength;
 998   char *curtmp;
 999   char *hdr_attr;
1000   long a,b;
1001 
1002 
1003   /* We need to insert the header title into a STRINGLIST structure, as
1004    * this is the type that must be supplied to mail_fetchheader_full.
1005    */
1006 
1007   cur->text.size = strlen ((char *) (cur->text.data = (unsigned char *) 
1008                                      cpystr (hdr_title)));
1009   
1010   /* If we don't want to return the header title, but only the contents,
1011    * this offset allows us to strip the header title. The "magic number" 2
1012    * is the string ": " of the header.
1013    * This method is uneffective for multiple headers (ex. Cc, Reply-To, etc.).
1014    */
1015   
1016   offset = cur->text.size + 2;
1017   
1018   /* Get the header line, if it exists */
1019   
1020   curtmp = mail_fetchheader_full (stream,mesgno,cur,NIL,NIL);
1021 
1022   tmplength = strlen(curtmp);
1023   hdr_attr = (char *)UT_malloc(tmplength + 4);
1024   
1025   /* cur contains the header title string, like "From:", "Subject:" etc.
1026    * tmplength is the length of the corresponding header line extracted
1027    * from the message. If a real line is returned, the header title
1028    * ("From:", "Subject:" etc.) will be contained within, hence
1029    * tmplength >= cur->text.size . This means that if
1030    * (cur->text.size > tmplength), no such header is present in the mail:
1031    * we must return an (almost) empty string.
1032    */
1033   
1034   a = (long)tmplength;
1035   b = (long)cur->text.size;
1036   if (a > b)
1037     {
1038       /* If we want to strip the header */
1039       /*sprintf (hdr_attr,"%s",curtmp + offset); */
1040       sprintf (hdr_attr,"%s",curtmp);
1041       /* printf ("%s",hdr_attr); */
1042     }
1043   else
1044     {
1045       sprintf (hdr_attr,"\n\n");
1046     }
1047   
1048   return (hdr_attr);
1049 } /* get_header_line() */
1050 
1051 
1052 
1053 
1054 /* Subroutine for writing in a file */
1055 
1056 void write_file (char *filename, char *text, size_t text_size)
     /* [<][>][^][v][top][bottom][index][help] */
1057 {
1058 
1059   FILE *fd;
1060   size_t i;
1061 
1062   /* printf ("%s\n",filename); */
1063   
1064   if ((fd = fopen(filename,"w")) != NULL)
1065     {
1066       for (i = 0; i < text_size; i++)
1067         if (text[i] != 13) 
1068           fprintf (fd, "%c",text[i]);
1069       fclose(fd);
1070     }
1071   else
1072     {
1073       ER_perror(FAC_MM, MM_CANTOPEN, "%s for writing\n",filename);
1074       die;
1075     }
1076   
1077 } /* write_file() */
1078 
1079 
1080 void read_file (const char *filename)
     /* [<][>][^][v][top][bottom][index][help] */
1081 {
1082 
1083   FILE *fd;
1084   int c;
1085 
1086   if ((fd = fopen (filename,"r")) != NULL)
1087     {
1088       while ((c = getc(fd)) != EOF)
1089         putc (c, stdout);
1090       fclose (fd);
1091     }
1092   else
1093     {
1094       ER_perror(FAC_MM, MM_CANTOPEN, "%s for reading\n",filename);
1095       die;
1096     }
1097 
1098 } /* read_file() */
1099 
1100 
1101 void put_in_file (char *fileprefix, char *extension, char *text, size_t text_size)
     /* [<][>][^][v][top][bottom][index][help] */
1102 {
1103 
1104   char filename[FILENAMELEN];
1105 
1106 
1107   /* Write in a file */
1108   
1109   sprintf (filename,"%s-%s",fileprefix,extension);
1110   /* printf ("%s\n",filename); */
1111   
1112   write_file(filename,text,text_size);
1113   
1114 }/* put_in_file() */
1115 
1116 
1117 /* Stolen from which_keytypes.c and converted to use regex.h instead of libgen.h */
1118 
1119 
1120 int do_regex_test (const char *pattern, char *string)
     /* [<][>][^][v][top][bottom][index][help] */
1121 {
1122 
1123   int match = 0;
1124 
1125   /* These are not used, since REG_NOSUB is specified in regcomp() */
1126   size_t nmatch = 0;
1127   regmatch_t pmatch[1];
1128 
1129   regex_t *re;
1130 
1131   re = (regex_t *)UT_malloc(STR_XL);
1132 
1133   regcomp(re, pattern, REG_NOSUB || REG_NEWLINE);
1134   if (regexec(re, string, nmatch, pmatch, 0))
1135     match = 0;
1136   else
1137     match = 1;
1138 
1139   regfree(re);
1140 
1141   /* Caution! regfree() does not do this job... */
1142   free(re);
1143 
1144   return(match);
1145 
1146 } /* do_regex_test() */
1147 
1148 
1149 /* Interfaces to c-client.
1150  * They must be here for the code to be compiled,
1151  * but most can stay empty.
1152  */
1153 
1154 void mm_searched (MAILSTREAM *stream,unsigned long number)
     /* [<][>][^][v][top][bottom][index][help] */
1155 {
1156 }
1157 
1158 
1159 void mm_exists (MAILSTREAM *stream,unsigned long number)
     /* [<][>][^][v][top][bottom][index][help] */
1160 {
1161 }
1162 
1163 
1164 void mm_expunged (MAILSTREAM *stream,unsigned long number)
     /* [<][>][^][v][top][bottom][index][help] */
1165 {
1166 }
1167 
1168 
1169 void mm_flags (MAILSTREAM *stream,unsigned long number)
     /* [<][>][^][v][top][bottom][index][help] */
1170 {
1171 }
1172 
1173 void mm_notify (MAILSTREAM *stream,char *string,long errflg)
     /* [<][>][^][v][top][bottom][index][help] */
1174 {
1175 }
1176 
1177 void mm_list (MAILSTREAM *stream,int delimiter,char *mailbox,long attributes)
     /* [<][>][^][v][top][bottom][index][help] */
1178 {
1179 }
1180 
1181 void mm_lsub (MAILSTREAM *stream,int delimiter,char *mailbox,long attributes)
     /* [<][>][^][v][top][bottom][index][help] */
1182 {
1183 }
1184 
1185 void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
     /* [<][>][^][v][top][bottom][index][help] */
1186 {
1187 }
1188 
1189 void mm_log (char *string,long errflg)
     /* [<][>][^][v][top][bottom][index][help] */
1190 {
1191   switch ((short) errflg) {
1192   case NIL:
1193     ER_dbg_va (FAC_MM, ASP_MM_GEN, "[%s]",string);
1194     break;
1195   case PARSE:
1196   case WARN:
1197     ER_perror (FAC_MM, MM_WARNCCL, "%%%s",string);
1198     break;
1199   case ERROR:
1200     ER_perror (FAC_MM, MM_ERRCCL, "%s",string);
1201     break;
1202   }
1203 }
1204 
1205 void mm_dlog (char *string)
     /* [<][>][^][v][top][bottom][index][help] */
1206 {
1207   puts (string);
1208 }
1209 
1210 void mm_login (NETMBX *mb,char *user,char *pwd,long trial)
     /* [<][>][^][v][top][bottom][index][help] */
1211 {
1212 }
1213 
1214 void mm_critical (MAILSTREAM *stream)
     /* [<][>][^][v][top][bottom][index][help] */
1215 {
1216 }
1217 
1218 void mm_nocritical (MAILSTREAM *stream)
     /* [<][>][^][v][top][bottom][index][help] */
1219 {
1220 }
1221 
1222 long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
     /* [<][>][^][v][top][bottom][index][help] */
1223 {
1224 #if UNIXLIKE
1225   kill (getpid (),SIGSTOP);
1226 #else
1227   abort ();
1228 #endif
1229   return NIL;
1230 }
1231 
1232 void mm_fatal (char *string)
     /* [<][>][^][v][top][bottom][index][help] */
1233 {
1234   ER_perror(FAC_MM, MM_FATCCL, "%s\n",string);
1235   die;
1236 }
1237 

/* [<][>][^][v][top][bottom][index][help] */