From: mathog@seqaxp.bio.caltech.edu Sent: Wednesday, June 20, 2001 4:07 PM To: Info-VAX@Mvb.Saic.Com Subject: mailextract.c, a VMS->UNIX mail file converter (works on 7.2-1) Recently I tried one of VMS->Unix mail file converters and it wouldn't work on my 7.2-1 alpha system. It was relying on carnal knowledge of the mail.mai file and something had changed. Rather than trying to fix that, I whipped up mailextract.c, which follows my signature. It seems to work correctly on the small number of mail.mai files I've tried it on. It converted all 4000 or so mail messages in my own mail.mai, in numerous folders, without any apparent errors. Attachments, if present, are not destroyed. It can't do much about DECNET mail, but it at least munges the "From " line into a format that Neomail and some others can handle so that you can try to figure out who sent it. I expect that there will be some forms of mail addresses it will hack to pieces. Regards, David Mathog mathog@caltech.edu Manager, sequence analysis facility, biology division, Caltech ********************************************************************** /* MAILEXTRACT.C This program extracts all mail messages from all folders from a specified MAIL.MAI and writes them as Unix compatible mail files. Build with: $ cc mailextract $ link mailextract $ mailextract :== $wherever:mailextract This is based on the examples in the OpenVMS Utility Routines Manual and from the PTMAIL program. Large chunks are copied from those sources verbatim or modified slightly. This is not a particularly clean piece of code because it was sewn together in a single day from these different sources which gives it a certain Frankenstein quality. Hopefully it will remain valid for some time as it uses the mail$ library routines. It takes mandatory parameter, which is the full path to the mail.mai file. Example: $ mailextract usrdisk:[users.fred]mail.mai Dumped 5 messages from folder ADDENDA Dumped 3 messages from folder MAIL Dumped 18 messages from folder NEWMAIL Total 26 messages in 3 folders David Mathog, mathog@caltech.edu Biology Division, Caltech 18-JUN-2001 Sorry to say, this is probably the last piece of code I'll ever write for this OS :-(. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAIL$_NOMOREREC 8314792 #define MAIL$_NOTEXIST 8290522 #define MAIL$_NOMOREMSG 8290386 #define MAIL$_NOSUCHUSR 8290346 #define MAIL$_USERSPEC 8290402 /* True and false definitions */ #define TRUE 1 #define FALSE 0 #define YES 1 #define NO 0 #define vms_ok(x) ((x) & 1) /* structures and typedefs */ typedef struct itmlst { short buffer_length; short item_code; void *buffer_address; long *return_length_address; } ITMLST; typedef struct { short unsigned int buf_len; short unsigned int item_code; char *buf_addr; unsigned int *ret_len_addr; } vms_item; typedef struct { short buffer_length; short item_code; void *buffer_address; long *return_length_address; } itemlist; struct list { struct list *next; char *foldername; long message_count; }; typedef struct msg_entry { struct msg_entry *prev; struct msg_entry *next; long id; long size; char date[NAM$C_MAXRSS+1]; char from[NAM$C_MAXRSS+1]; char reply[NAM$C_MAXRSS+1]; char subject[NAM$C_MAXRSS+1]; char dest[NAM$C_MAXRSS+1]; long flags; } msg_entry; /* prototypes */ void open_message(void); /* originally in mail.c */ void close_message(void); /* ditto */ int decc$free(void *ptr); /* prototype for decc$free */ extern int mail$message_begin(); extern int mail$message_end(); extern int mail$message_select(); extern int mail$message_get(); extern int mail$message_copy(); extern int mail$message_info(); extern int mail$mailfile_begin(); extern int mail$mailfile_end(); extern int mail$mailfile_open(); extern int mail$mailfile_close(); extern int mail$mailfile_compress(); extern int mail$mailfile_purge_waste(); extern int mail$mailfile_info_file(); extern int mail$user_begin(); extern int mail$user_end(); extern int mail$user_get_info(); extern int mail$user_set_info(); extern int mail$send_begin(); extern int mail$send_end(); extern int mail$send_abort(); extern int mail$send_add_address(); extern int mail$send_add_attribute(); extern int mail$send_add_bodypart(); extern int mail$send_message(); /* globals */ msg_entry *gmsgs = NULL; msg_entry *tmp_msg = NULL; int message_context = 0; int file_context = 0; struct list *tmp = NULL; void loud_free( void *ptr){ #include #include if(decc$free(ptr) != 0){ (void) printf("illegal free() operation\n"); (void) lib$signal(SS$_ACCVIO); } } /*spacetounderscore replaces all spaces with underscores */ char * spacetounderscore(char *string){ char *p; for(p=string;*p!='\0';p++){ if(*p==' ')*p='_'; } return(string); } /* Noquotes, works back from the end of a string and extracts the part that is inside of quotes. It assumes that all quotes are matched and that there aren't multiple quotes. That is, it will convert smtp%"adress" to address, but whacko things like "blah"@wherever will be mangled. This does modify the input string!!! DECNET is set to 1 if there was no "@" encountered */ char * noquotes(char *string, int *decnet){ int pos; int state; /* 1 looking for last quote, 0 looking for matching */ char * toret; *decnet = 1; /* default to is decnet message */ for(state=1, pos = strlen(string)-1;pos >= 0 ; pos--){ if(string[pos] == '@')*decnet=0; if(string[pos] == '"'){ if(state == 1){ string[pos] = '\0'; state = 0; } else { toret = &string[pos+1]; return(toret); } } } return(string); } #include #include #include #include #include #include #include /* originally was routine asccm_to_bin() from Michael G. Miller "Just another number in the grand Ford Motor Credit Company design." -- Boston mmiller4@fc.ford.com modified to take in dates in this format: 11-FEB-1992 08:03:16.75 and write them back out again as Mon Jun 18 07:39:44 2001 */ char *uformat(char *string) { #define BIGENOUGH 200 static char in_ptr[BIGENOUGH]; static char out_ptr[BIGENOUGH]; static int usr_cntxt_in; static int usr_cntxt_out; int st = 0; struct dt_default_fields flags = {1, 1, 1, 1, 1, 1, 1}; $DESCRIPTOR(in_dt, in_ptr); $DESCRIPTOR(out_dt, out_ptr); char timeslot[8]; char *bin=timeslot; /* a place to store the 8 bytes of time */ (void) strcpy(in_ptr,string); in_dt.dsc$w_length = strlen(in_ptr); out_dt.dsc$w_length = BIGENOUGH - 4; /* Initialize the user context for the input date format DD-MMM-YYYY hh:mm:ss.cc and the output data format Mon DD hh:mm:ss YYYY */ if(usr_cntxt_in == 0){ $DESCRIPTOR(ini_str, "|!DB-!MAAU-!Y4 !HH4:!MM:!SS.!C2|"); st = LIB$INIT_DATE_TIME_CONTEXT(&usr_cntxt_in, &LIB$K_INPUT_FORMAT, &ini_str); $DESCRIPTOR(ini_str2, "|!WAC !MAAC !D0 !H04:!M0:!S0 !Y4|"); /* 3 1 3 1 2 1 21 2 1 2 1 4 = 24 */ st = LIB$INIT_DATE_TIME_CONTEXT(&usr_cntxt_out, &LIB$K_OUTPUT_FORMAT, &ini_str2); } /* convert to binary format */ st = LIB$CONVERT_DATE_STRING(&in_dt, bin, &usr_cntxt_in, &flags, 0, 0); /* convert back to string format */ st = LIB$FORMAT_DATE_TIME(&out_dt, bin, &usr_cntxt_out, &flags, 0, 0); out_ptr[24]='\0'; /* null terminate */ return out_ptr; } void write_to_file(FILE *f, char *quote, char *orig_str) { #define WRAPAT 131 char *lstr = orig_str; char *p; while( strlen(lstr) > WRAPAT ) { p = &lstr[WRAPAT-1]; if( *p == ' ' || *p == '\t' || p == lstr ) { *(p++) = '\0'; } else { while( *(--p) != ' ' && *p != '\t' && p != lstr ) ; if( p == lstr ) { char tmp[WRAPAT+1]; (void) strncpy(tmp, lstr, WRAPAT); tmp[WRAPAT] = '\0'; if(strncasecmp(tmp,"from ",5)==0){ (void) fprintf(f, ">%s%s\n", quote, tmp); } else { (void) fprintf(f, "%s%s\n", quote, tmp); } lstr = &lstr[WRAPAT]; continue; } *(p++) = '\0'; } /* Skip over leading whitespace of next line. */ while( *p == ' ' || *p == '\t' ) p++; (void) fprintf(f, "%s%s\n", quote, lstr); lstr = p; } if(strncasecmp(lstr,"from ",5)==0){ (void) fprintf(f, ">%s%s\n", quote, lstr); } else { (void) fprintf(f, "%s%s\n", quote, lstr); } } /* dump_msg will get a message from VMS MAIL by using the message_id and then dump it into a given file. It has the option to leave out the headers and/or put a quote in front of the lines. */ int dump_msg(msg_entry *message_data, char *quote, char *filename, int showheaders, int *newfile) { char message[NAM$C_MAXRSS+2]; long mlen, total = 0; static FILE *f=NULL; char ufromline[200]; char *dequoted; int decnet; itemlist null_list[1]; itemlist itmlst_select[2]; itemlist itmlst_in[2]; itemlist itmlst_out[2]; null_list[0].buffer_length = 0; null_list[0].item_code = 0; null_list[0].buffer_address = 0; null_list[0].return_length_address = 0; itmlst_select[0].buffer_length = 4; itmlst_select[0].item_code = MAIL$_MESSAGE_ID; itmlst_select[0].buffer_address = &message_data->id; itmlst_select[0].return_length_address = 0; itmlst_select[1].buffer_length = 0; itmlst_select[1].item_code = 0; itmlst_select[1].buffer_address = 0; itmlst_select[1].return_length_address = 0; itmlst_in[0].buffer_length = 0; itmlst_in[0].item_code = MAIL$_MESSAGE_CONTINUE; itmlst_in[0].buffer_address = 0; itmlst_in[0].return_length_address = 0; itmlst_in[1].buffer_length = 0; itmlst_in[1].item_code = 0; itmlst_in[1].buffer_address = 0; itmlst_in[1].return_length_address = 0; itmlst_out[0].buffer_length = NAM$C_MAXRSS; itmlst_out[0].item_code = MAIL$_MESSAGE_RECORD; itmlst_out[0].buffer_address = message; itmlst_out[0].return_length_address = &mlen; itmlst_out[1].buffer_length = 0; itmlst_out[1].item_code = 0; itmlst_out[1].buffer_address = 0; itmlst_out[1].return_length_address = 0; mlen = 0; /* it has initialization problems otherwise */ if (!vms_ok(mail$message_get(&message_context, itmlst_select, null_list))) { (void) fprintf(stderr, "Error in mail$message_get [dump_msg]\n"); exit(SS$_NORMAL); } /* construct the unix from line (ufromline) */ (void) strcpy(ufromline,"From "); /* this is case sensitive! */ dequoted = noquotes(message_data->from, &decnet); /* decnet = 1 means a decnet message */ if(decnet == 0){ /* smtp message */ (void) strcat(ufromline,dequoted); } else { (void) strcat(ufromline,spacetounderscore(dequoted)); (void) strcat(ufromline,"@was.decnet.mail"); } (void) strcat(ufromline," "); (void) strcat(ufromline,uformat(message_data->date)); /* Open a file to write to... */ if(*newfile != 0){ if (f != NULL)(void) fclose(f); if (!(f = fopen(filename, "w"))){ return(0); } *newfile=0; (void) fprintf(f,"%s\n",ufromline); } else { (void) fprintf(f,"\n%s\n",ufromline); } /* decnet mail has no headers in message, fake them using those from structure. There is no way to reply to this mail, but at least we'll know who, when, and what. */ if(decnet == 1){ (void) fprintf(f,"From: %s@was.decnet.mail\n",spacetounderscore(dequoted)); (void) fprintf(f,"Subject: %s\n",message_data->subject); (void) fprintf(f,"Date: %s\n\n",message_data->date); } /* Get each line in the message */ while (mail$message_get(&message_context, itmlst_in, itmlst_out) != MAIL$_NOMOREREC) { if ((total <= 5) && /* Skip 1st 5 lines and all headers */ ((strstr(message, "Return-path:")) || (strstr(message, "Received:"))) && !showheaders) { while (mlen != 0) { /* until there is an empty line */ if (mail$message_get(&message_context, itmlst_in, itmlst_out) == MAIL$_NOMOREREC){ (void) fprintf(f, "\n"); /* one line between messages */ break; } } } else { total++; /* increase the line counter */ message[mlen] = 0; /* Truncate the message */ /* And print it to the file with a insertion */ if (total > 5 || showheaders) write_to_file(f, quote, message); } /* End of if ((tota..else */ } /* End of while... */ return(1); /* And return an 'ok' */ } /* get_and_dump_msgs will take a foldername and will retrieve each message and then dump it to a file named foldername. */ void get_msgs(char *foldername, long *msg_count) { msg_entry the_msg; itemlist itmlst_folder_in[3]; itemlist itmlst_folder_out[2]; itemlist itmlst_message[7]; itemlist itmlst_next_message[3]; long counter, status; msg_entry *tmp_msg, *last_msg, *msgs = 0; int isnewfolder; *msg_count = 0; isnewfolder = 1; itmlst_folder_in[0].buffer_length = strlen(foldername); itmlst_folder_in[0].item_code = MAIL$_MESSAGE_FOLDER; itmlst_folder_in[0].buffer_address = foldername; itmlst_folder_in[0].return_length_address = 0; itmlst_folder_in[1].buffer_length = 0; itmlst_folder_in[1].item_code = MAIL$_NOSIGNAL; itmlst_folder_in[1].buffer_address = 0; itmlst_folder_in[1].return_length_address = 0; itmlst_folder_in[2].buffer_length = 0; itmlst_folder_in[2].item_code = 0; itmlst_folder_in[2].buffer_address = 0; itmlst_folder_in[2].return_length_address = 0; itmlst_folder_out[0].buffer_length = 4; itmlst_folder_out[0].item_code = MAIL$_MESSAGE_SELECTED; itmlst_folder_out[0].buffer_address = msg_count; itmlst_folder_out[0].return_length_address = 0; itmlst_folder_out[1].buffer_length = 0; itmlst_folder_out[1].item_code = 0; itmlst_folder_out[1].buffer_address = 0; itmlst_folder_out[1].return_length_address = 0; open_message(); status = mail$message_select(&message_context, itmlst_folder_in, itmlst_folder_out); if ((status == MAIL$_NOTEXIST) || (*msg_count == 0)) { *msg_count = 0; /* What? no msgs??? Oh well */ return; } /* End of if (statu.. */ if (!vms_ok(status)) { (void) fprintf(stderr, "Error in mail$message_select [msg_entry]\n"); exit(SS$_NORMAL); } last_msg = 0; /* Let loop through all the messages */ for (counter = 0; counter < *msg_count; counter++) { tmp_msg = &the_msg; (void) memset(tmp_msg, 0, sizeof(msg_entry)); itmlst_message[0].buffer_length = 4; itmlst_message[0].item_code = MAIL$_MESSAGE_CURRENT_ID; itmlst_message[0].buffer_address = &tmp_msg->id; itmlst_message[0].return_length_address = 0; itmlst_message[1].buffer_length = NAM$C_MAXRSS; itmlst_message[1].item_code = MAIL$_MESSAGE_DATE; itmlst_message[1].buffer_address = tmp_msg->date; itmlst_message[1].return_length_address = 0; itmlst_message[2].buffer_length = 4; itmlst_message[2].item_code = MAIL$_MESSAGE_SIZE; itmlst_message[2].buffer_address = &tmp_msg->size; itmlst_message[2].return_length_address = 0; itmlst_message[3].buffer_length = NAM$C_MAXRSS; itmlst_message[3].item_code = MAIL$_MESSAGE_FROM; itmlst_message[3].buffer_address = tmp_msg->from; itmlst_message[3].return_length_address = 0; itmlst_message[4].buffer_length = NAM$C_MAXRSS; itmlst_message[4].item_code = MAIL$_MESSAGE_REPLY_PATH; itmlst_message[4].buffer_address = tmp_msg->reply; itmlst_message[4].return_length_address = 0; itmlst_message[5].buffer_length = NAM$C_MAXRSS; itmlst_message[5].item_code = MAIL$_MESSAGE_SUBJECT; itmlst_message[5].buffer_address = tmp_msg->subject; itmlst_message[5].return_length_address = 0; itmlst_message[6].buffer_length = 0; itmlst_message[6].item_code = 0; itmlst_message[6].buffer_address = 0; itmlst_message[6].return_length_address = 0; itmlst_next_message[0].buffer_length = 0; itmlst_next_message[0].item_code = MAIL$_MESSAGE_NEXT; itmlst_next_message[0].buffer_address = 0; itmlst_next_message[0].return_length_address = 0; itmlst_next_message[1].buffer_length = 0; itmlst_next_message[1].item_code = MAIL$_NOSIGNAL; itmlst_next_message[1].buffer_address = 0; itmlst_next_message[1].return_length_address = 0; itmlst_next_message[2].buffer_length = 0; itmlst_next_message[2].item_code = 0; itmlst_next_message[2].buffer_address = 0; itmlst_next_message[2].return_length_address = 0; /* get the info! yeah... */ status = mail$message_info(&message_context, itmlst_next_message, itmlst_message); if (status == MAIL$_NOMOREMSG){ (void)printf("Last message retrieved was %d\n",counter); counter = *msg_count; /* no more messages? ok, we are done.. */ } else { if (!vms_ok(status)) { (void) fprintf(stderr, "Error in mail$message_info [msg_entry]\n"); exit(SS$_NORMAL); } /* if (!vms_ok... */ (void) dump_msg(tmp_msg, "", foldername, FALSE, &isnewfolder); } /* if (status ==... */ } /* for (counter... */ return; } /* End of get_msgs() */ msg_entry *clear_msgs(msg_entry *msgs) { msg_entry *tmp_msg; while (msgs != 0) { /* First give memory back if any is used */ tmp_msg = msgs; msgs = tmp_msg->next; loud_free(tmp_msg); } return(msgs); } /* open_message will open the MAIL file and get the message context */ void open_message(void) { int locstatus; itemlist null_list[1]; itemlist itmlst_begin[3]; null_list[0].buffer_length = 0; null_list[0].item_code = 0; null_list[0].buffer_address = 0; null_list[0].return_length_address = 0; itmlst_begin[0].buffer_length = sizeof(file_context); itmlst_begin[0].item_code = MAIL$_MESSAGE_FILE_CTX; itmlst_begin[0].buffer_address = &file_context; itmlst_begin[0].return_length_address = 0; itmlst_begin[1].buffer_length = 0; itmlst_begin[1].item_code = MAIL$_NOSIGNAL; itmlst_begin[1].buffer_address = 0; itmlst_begin[1].return_length_address = 0; itmlst_begin[2].buffer_length = 0; itmlst_begin[2].item_code = 0; itmlst_begin[2].buffer_address = 0; itmlst_begin[2].return_length_address = 0; if (message_context != 0) close_message(); locstatus=mail$message_begin(&message_context, itmlst_begin, null_list); } /* End of open_message() */ /* close_message will close the MAIL file */ void close_message(void) { itemlist null_list[1]; null_list[0].buffer_length = 0; null_list[0].item_code = 0; null_list[0].buffer_address = 0; null_list[0].return_length_address = 0; (void) mail$message_end(message_context, null_list, null_list); message_context = 0; } /* End of close_message */ int folder_routine(struct list *mlist, struct dsc$descriptor *name) { if (name->dsc$w_length) { while (mlist->next) mlist = mlist->next; mlist->next = malloc(sizeof(struct list)); mlist = mlist->next; mlist->next = 0; mlist->foldername = malloc(name->dsc$w_length + 1); (void) strncpy(mlist->foldername,name->dsc$a_pointer,name->dsc$w_length); mlist->foldername[name->dsc$w_length] = '\0'; } return(SS$_NORMAL); } int main (int argc, char *argv[]) { static struct list mlist = {NULL,NULL,0}; char themailfile[255]; int messages_selected = 0; int total_folders = 0; int total_messages = 0; int isnewfolder; /* this assumes that argv[1] is the name of the file. If none is supplied, the current user's is used */ if(argc != 2){ (void) fprintf(stderr,"useage: $ mailextract disk:[dir.user]mail.mai\n"); exit(EXIT_FAILURE); } else { (void) strcpy(themailfile,argv[1]); } ; ITMLST nulllist[] = {{0,0,0,0}}, message_in_itmlst[] = { {sizeof(file_context),MAIL$_MESSAGE_FILE_CTX, &file_context,0}, {0,0,0,0}}, message_mailfile_itmlst[] = { {strlen(themailfile),MAIL$_MAILFILE_DEFAULT_NAME,themailfile,0}, {0,0,0,0}}, mailfile_info_itmlst[] = { {4,MAIL$_MAILFILE_FOLDER_ROUTINE, (void *)folder_routine,0}, {4,MAIL$_MAILFILE_USER_DATA,&mlist,0}, {0,0,0,0}}, message_select_in_itmlst[] = { {0,MAIL$_MESSAGE_FOLDER,0,0}, {0,0,0,0}}, message_select_out_itmlst[] = { {sizeof(messages_selected),MAIL$_MESSAGE_SELECTED,&messages_selected,0}, {0,0,0,0}}; if ( (mail$mailfile_begin(&file_context, nulllist, nulllist) == SS$_NORMAL) && ( mail$mailfile_open(&file_context, message_mailfile_itmlst, nulllist) == SS$_NORMAL) && (SS$_NORMAL == mail$mailfile_info_file(&file_context, mailfile_info_itmlst,nulllist)) && (SS$_NORMAL == mail$message_begin(&message_context, message_in_itmlst,nulllist)) ) { tmp = &mlist; while(tmp->next) { tmp = tmp->next; message_select_in_itmlst[0].buffer_address = tmp->foldername; message_select_in_itmlst[0].buffer_length = strlen(tmp->foldername); if (mail$message_select(&message_context, message_select_in_itmlst, message_select_out_itmlst) == SS$_NORMAL) { /* (void) printf("Folder %s has %d messages\n", tmp->foldername, messages_selected); */ total_messages += messages_selected; total_folders++; } } } /* Read in all the messages in each folder and output to a file */ for(tmp = mlist.next; tmp!=NULL; tmp = tmp->next){ if(tmp->foldername == NULL){ (void) printf("Oops, fatal error, folder with no name\n"); exit(EXIT_FAILURE); } gmsgs = clear_msgs(gmsgs); get_msgs(tmp->foldername, &tmp->message_count); (void) printf("Dumped %6d messages from folder %s\n",tmp->message_count,tmp->foldername); } (void) printf("Total %6d messages in %d folders\n",total_messages, total_folders); (void) mail$message_end(&message_context, nulllist, nulllist); (void) mail$mailfile_close(&file_context, nulllist, nulllist); (void) mail$mailfile_end(&file_context, nulllist, nulllist); exit (EXIT_SUCCESS); }