diff -Ncr pine4.53/imap/src/c-client/mail.c pine4.53-smime/imap/src/c-client/mail.c *** pine4.53/imap/src/c-client/mail.c Thu Jan 2 17:28:42 2003 --- pine4.53-smime/imap/src/c-client/mail.c Wed Jan 15 12:48:15 2003 *************** *** 5198,5203 **** --- 5198,5207 ---- void mail_free_body_data (BODY *body) { + /* cleanup body if requested by application */ + if (body->cleanup) + (*body->cleanup)(body); + switch (body->type) { /* free contents */ case TYPEMULTIPART: /* multiple part */ mail_free_body_part (&body->nested.part); diff -Ncr pine4.53/imap/src/c-client/mail.h pine4.53-smime/imap/src/c-client/mail.h *** pine4.53/imap/src/c-client/mail.h Tue Jan 7 12:33:50 2003 --- pine4.53-smime/imap/src/c-client/mail.h Wed Jan 15 12:48:15 2003 *************** *** 655,660 **** --- 655,663 ---- unsigned long bytes; /* size of text in octets */ } size; char *md5; /* MD5 checksum */ + + void *sparep; /* spare pointer reserved for main program */ + void (*cleanup)(BODY *); /* cleanup function */ }; diff -Ncr pine4.53/pine/README.smime pine4.53-smime/pine/README.smime *** pine4.53/pine/README.smime Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/README.smime Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,129 ---- + Quick Start + =========== + + To enable S/MIME support, ensure the SSL related lines in the platform + makefile are uncommented (they're all next to one another). + + CA certificates are expected to be found in the OpenSSL 'cert' dir, in the + standard hashed format. + + User Configuration + ================== + + A directory '~/.pine-smime' must be created. Within this, a further + three directories are required: + + ~/.pine-smime/ + ca/ + private/ + public/ + + Other people's certificate files should be copied into the 'public' directory. + Your certificate file(s) should be copied into the 'private' directory. + Certificates for any additional trusted CAs should be put in the 'ca' directory. + + There are three extra configuration options: + + sign-default-on + encrypt-default-on + remember-smime-passphrase + + Certificates + ============ + + The certificate files specified above should have the following form: + + public certificates: user@emaildomain.crt + private keys: user@emaildomain.key + + Thus, a typical installation might look like this: + + ~/.pine-smime/ + ca/ + [additional trusted CAs here] + private/ + paisleyj@dcs.gla.ac.uk.crt + paisleyj@dcs.gla.ac.uk.key + public/ + myfriend@dcs.gla.ac.uk.crt + myotherfriend@dcs.gla.ac.uk.crt + + Implementation Details + ====================== + + Link with the OpenSSL crypto library for PKCS7 support. + Only tested on linux (slx) and solaris (so5). + + Added three extra source files (+headers): + + smime.c Main S/MIME support code + smkeys.c Very basic X509 key handling/storage (using the above dirs) + bss_so.c OpenSSL BIO using pine STORE_S objects + + Patches to existing pine sources: + + init.c + Add references to new configuration options. + + mailcmd.c + Add implementation of MC_DECRYPT command which prompts + the user for a passphrase if it's required. + + mailpart.c + Comment added to help me remember what I'd done. + + mailview.c + Added description of Decrypt menu option. + Make calls out to smime.c functions to handle the decryption. + This is done shortly after the BODY of a message is + obtained. + Added function to describe encrypted messages when they're + being displayed. + Added code to describe the special case of PKCS7 attachments. + + makefile.lnx + makefile.so5 + Added SSL variables etc. + + pine.h + Add enumerations for new configuration options and definition + of MC_DECRYPT command + Exported the prototype of pine_write_body_header, + pine_rfc822_output_body and pine_encode_body since they're + needed in smime.c. + + pine.hlp + Added help info for new configuration options. + + send.c + Added 'Encrypt' and 'Sign' menu options when sending email. + Make calls to smime.c functions to fiddle message on the + way out. + Extend pine_encode_body so it makes a few more checks + before adding a boundary. + + Basic method: + + Incoming + + Scan BODY of viewed message before it is formatted. If it contains + PKCS7 parts, decode them and attempt to decrypt/verify signatures. The + original BODY is fiddled in place and turned into a multipart body + with one subpart -- the decrypted data. This may consist of a multipart + with attachments, for example. + + This all depends on stashing a pointer to the decrypted data in + body->contents.text.data and relying on the fact that the mail_* routines + will use this data in preference to fetching it over the network. We + also depend on it not being garbage collected while the message is + being viewed! + + Outgoing + + smime.c pre-builds the message using pine_encode_body, pine_write_body_header + and pine_rfc822_output_body, encrypting/signing the resulting data. The + body that was going to be sent is then fiddled appropriately after + the PKCS7 objects have been built. + + paisleyj@dcs.gla.ac.uk + Mar 7 2001 diff -Ncr pine4.53/pine/TODO.smime pine4.53-smime/pine/TODO.smime *** pine4.53/pine/TODO.smime Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/TODO.smime Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,31 ---- + + Need to be able to view stored certificates to see details + (particularly the fingerprint for comparing over the phone, say) + --> proper key management system + + Add client private key and certificate request generation. + + Send certificate for CA along with certificate of signer. + + Verify recipient certificate before sending encrypted message. + + Verify certificates in general. + + Cache the result of pre-formatting the message during the send/encrypt/sign + phase rather than letting call_mailer re-format it all over again. + + Tidy up the use of global variables considerably. + + Intelligently pick a certificate for signing purposes based on the + From address rather than just picking the first one on the list. + + Figure out platform dependancies from using readdir() in smkeys.c + + Handle message/rfc822 sub-parts! + + Consider what happens with all our cached data. + + S/MIME info screen help. + + paisleyj@dcs.gla.ac.uk + Mar 7 2001 diff -Ncr pine4.53/pine/bss_so.c pine4.53-smime/pine/bss_so.c *** pine4.53/pine/bss_so.c Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/bss_so.c Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,184 ---- + #ifdef SMIME + + /* + bss_so.c + + Basic implementation of an OpenSSL BIO which is + backed by a pine STORE_S object + */ + + #include "headers.h" + + #include + #include + #include + #include + + static int bss_so_write(BIO *h, char *buf, int num); + static int bss_so_read(BIO *h, char *buf, int size); + static int bss_so_puts(BIO *h, char *str); + static int bss_so_gets(BIO *h, char *str, int size); + static long bss_so_ctrl(BIO *h, int cmd, long arg1, char *arg2); + static int bss_so_new(BIO *h); + static int bss_so_free(BIO *data); + static BIO_METHOD methods_sop = + { + BIO_TYPE_MEM, + "Storage Object", + bss_so_write, + bss_so_read, + bss_so_puts, + bss_so_gets, + bss_so_ctrl, + bss_so_new, + bss_so_free, + NULL, + }; + + BIO *BIO_new_so(STORE_S *so) + { + BIO *ret; + + if ((ret = BIO_new(&methods_sop)) == NULL) + return (NULL); + + BIO_set_fp(ret, (FILE*) so, 0); + return (ret); + } + + + static int bss_so_new(BIO *bi) + { + bi->init = 0; + bi->num = 0; + bi->ptr = NULL; + return (1); + } + + static int bss_so_free(BIO *a) + { + if (a == NULL) return (0); + if (a->shutdown) { + if ((a->init) && (a->ptr != NULL)) { + a->ptr = NULL; + } + a->init = 0; + } + return (1); + } + + static int bss_so_read(BIO *b, char *out, int outl) + { + int ret = 0; + STORE_S *so = (STORE_S*) b->ptr; + + if (b->init && (out != NULL)) { + + while (ret < outl) { + if (!so->readc((unsigned char *)out, so)) + break; + out++; + ret++; + } + + } + return (ret); + } + + static int bss_so_write(BIO *b, char *in, int inl) + { + int ret = 0; + + if (b->init && (in != NULL)) { + if (so_nputs((STORE_S *)b->ptr, in, inl)) + ret = inl; + + } + return (ret); + } + + static long bss_so_ctrl(BIO *b, int cmd, long num, char *ptr) + { + long ret = 1; + STORE_S *so = (STORE_S *)b->ptr; + FILE **fpp; + char p[4]; + + switch (cmd) { + case BIO_C_FILE_SEEK: + case BIO_CTRL_RESET: + ret = so_seek(so, num, 0); + break; + case BIO_CTRL_EOF: + ret = 0; + break; + case BIO_C_FILE_TELL: + case BIO_CTRL_INFO: + ret = 0; + break; + case BIO_C_SET_FILE_PTR: + bss_so_free(b); + b->shutdown = (int)num & BIO_CLOSE; + b->ptr = (char *)ptr; + b->init = 1; + break; + case BIO_C_SET_FILENAME: + ret = 0; + break; + case BIO_C_GET_FILE_PTR: + if (ptr != NULL) { + fpp = (FILE **)ptr; + *fpp = (FILE *)NULL; + } + break; + case BIO_CTRL_GET_CLOSE: + ret = (long)b->shutdown; + break; + case BIO_CTRL_SET_CLOSE: + b->shutdown = (int)num; + break; + case BIO_CTRL_FLUSH: + break; + case BIO_CTRL_DUP: + ret = 1; + break; + + case BIO_CTRL_WPENDING: + case BIO_CTRL_PENDING: + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + default: + ret = 0; + break; + } + return (ret); + } + + static int bss_so_gets(BIO *bp, char *buf, int size) + { + int ret = 0; + char *b = buf; + char *bend = buf + size - 1; + STORE_S *so = (STORE_S*) bp->ptr; + + do { + if (!so->readc((unsigned char *)b, so)) + break; + b++; + } while (b < bend && b[ -1] != '\n'); + + *b = 0; + + ret = b - buf; + return (ret); + } + + static int bss_so_puts(BIO *bp, char *str) + { + STORE_S *so = (STORE_S*) bp->ptr; + + return so->puts(so, str); + } + + + #endif /* SMIME */ diff -Ncr pine4.53/pine/bss_so.h pine4.53-smime/pine/bss_so.h *** pine4.53/pine/bss_so.h Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/bss_so.h Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1 ---- + BIO *BIO_new_so(STORE_S *so); diff -Ncr pine4.53/pine/filter.c pine4.53-smime/pine/filter.c *** pine4.53/pine/filter.c Wed Jan 8 12:13:50 2003 --- pine4.53-smime/pine/filter.c Wed Jan 15 12:48:15 2003 *************** *** 903,909 **** /* get a character from a file */ ! /* assumes gf_out struct is filled in */ int gf_freadc(c) unsigned char *c; --- 903,909 ---- /* get a character from a file */ ! /* assumes gf_in struct is filled in */ int gf_freadc(c) unsigned char *c; *************** *** 938,944 **** /* get a character from a string, return nonzero if things OK */ ! /* assumes gf_out struct is filled in */ int gf_sreadc(c) unsigned char *c; --- 938,944 ---- /* get a character from a string, return nonzero if things OK */ ! /* assumes gf_in struct is filled in */ int gf_sreadc(c) unsigned char *c; diff -Ncr pine4.53/pine/init.c pine4.53-smime/pine/init.c *** pine4.53/pine/init.c Mon Jan 6 19:39:16 2003 --- pine4.53-smime/pine/init.c Wed Jan 15 12:48:15 2003 *************** *** 2445,2451 **** F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND}, {"use-sender-not-x-sender", F_USE_SENDER_NOT_X, h_config_use_sender_not_x, PREF_SEND}, ! /* Folder */ {"combined-subdirectory-display", F_CMBND_SUBDIR_DISP, h_config_combined_subdir_display, PREF_FLDR}, --- 2445,2458 ---- F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND}, {"use-sender-not-x-sender", F_USE_SENDER_NOT_X, h_config_use_sender_not_x, PREF_SEND}, ! #ifdef SMIME ! {"sign-default-on", ! F_SIGN_DEFAULT_ON, h_config_sign_default_on, PREF_SEND}, ! {"encrypt-default-on", ! F_ENCRYPT_DEFAULT_ON, h_config_encrypt_default_on, PREF_SEND}, ! {"remember-smime-passphrase", ! F_REMEMBER_SMIME_PASSPHRASE, h_config_remember_smime_passphrase, PREF_SEND}, ! #endif /* Folder */ {"combined-subdirectory-display", F_CMBND_SUBDIR_DISP, h_config_combined_subdir_display, PREF_FLDR}, diff -Ncr pine4.53/pine/mailcmd.c pine4.53-smime/pine/mailcmd.c *** pine4.53/pine/mailcmd.c Fri Jan 10 15:29:29 2003 --- pine4.53-smime/pine/mailcmd.c Wed Jan 15 12:48:15 2003 *************** *** 53,58 **** --- 53,59 ---- #include "headers.h" #include "../c-client/imap4r1.h" + #include "smime.h" /* * Internal Prototypes *************** *** 1439,1444 **** --- 1440,1458 ---- break; + #ifdef SMIME + /*------- Try to decrypt message -----------*/ + case MC_DECRYPT: + if (g_need_passphrase) + get_passphrase(); + a_changed = TRUE; + break; + + case MC_SECURITY: + state->next_screen = smime_info_screen; + break; + #endif + /*------- Bounce -----------*/ case MC_BOUNCE : cmd_bounce(state, msgmap, 0); diff -Ncr pine4.53/pine/mailpart.c pine4.53-smime/pine/mailpart.c *** pine4.53/pine/mailpart.c Wed Nov 27 15:22:54 2002 --- pine4.53-smime/pine/mailpart.c Wed Jan 15 12:48:15 2003 *************** *** 4534,4539 **** --- 4534,4545 ---- frd->flags = flags; frd->size = size; frd->readc = fetch_readc; + + /* The call to imap_cache below will return true in the case where + we've already stashed fake data in the content of the part. + This happens when an S/MIME message is decrypted. + */ + if(modern_imap_stream(stream) && !imap_cache(stream, msgno, section, NULL, NULL) && size > INIT_FETCH_CHUNK diff -Ncr pine4.53/pine/mailview.c pine4.53-smime/pine/mailview.c *** pine4.53/pine/mailview.c Tue Jan 7 16:17:00 2003 --- pine4.53-smime/pine/mailview.c Wed Jan 15 12:48:15 2003 *************** *** 47,52 **** --- 47,53 ---- #include "headers.h" + #include "smime.h" /*---------------------------------------------------------------------- *************** *** 207,214 **** --- 208,220 ---- HELP_MENU, OTHER_MENU, + #ifdef SMIME + {"^D","Decrypt", {MC_DECRYPT,1,{ctrl('d')},KS_NONE}}, + {"^E","Security", {MC_SECURITY,1,{ctrl('e')},KS_NONE}}, + #else NULL_MENU, NULL_MENU, + #endif RCOMPOSE_MENU, NULL_MENU, NULL_MENU, *************** *** 227,232 **** --- 233,240 ---- #define BOUNCE_KEY 33 #define FLAG_KEY 34 #define VIEW_PIPE_KEY 35 + #define DECRYPT_KEY (VIEW_PIPE_KEY + 3) + #define SECURITY_KEY (DECRYPT_KEY + 1) static struct key simple_text_keys[] = {HELP_MENU, *************** *** 432,437 **** --- 440,447 ---- else ps->unseen_in_view = !mc->seen; + flags = 0; + #if defined(DOS) && !defined(WIN32) /* * Handle big text for DOS here. *************** *** 459,465 **** ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps) + SCROLL_LINES_BELOW(ps))); ! flags = FM_DISPLAY; if((last_message_viewed != mn_get_cur(ps->msgmap) || last_was_full_header == 1)) flags |= FM_NEW_MESS; --- 469,475 ---- ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps) + SCROLL_LINES_BELOW(ps))); ! flags |= FM_DISPLAY; if((last_message_viewed != mn_get_cur(ps->msgmap) || last_was_full_header == 1)) flags |= FM_NEW_MESS; *************** *** 467,472 **** --- 477,488 ---- if(offset) /* no pre-paint during resize */ view_writec_killbuf(); + #ifdef SMIME + /* Attempt to handle S/MIME bodies */ + if (fiddle_smime_message(body,raw_msgno,(flags&FM_NEW_MESS)!=0)) + flags |= FM_NEW_MESS; /* body was changed, force a reload */ + #endif + #ifdef _WINDOWS mswin_noscrollupdate(1); #endif *************** *** 541,546 **** --- 557,567 ---- if(F_OFF(F_ENABLE_FULL_HDR, ps_global)) clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap); + + #ifdef SMIME + if (!g_need_passphrase) + clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap); + #endif if(!handles){ /* *************** *** 754,760 **** --- 775,821 ---- } + #ifdef SMIME + /*---------------------------------------------------------------------- + Add descriptive lines to the top of a message being formatted + that describe the status of any S/MIME enclosures that + have been encountered. + + Args: body -- top-level body of the message being described + pc -- output function for writing to the message display + + ----*/ + static int describe_smime_bodies(BODY *body,gf_io_t pc) + { + PART *part; + int result = 0; + + if (!body) + return result; + + if (body->type == TYPEMULTIPART) { + + if (body->subtype && strucmp(body->subtype,"x-pkcs7-enclosure")==0) { + + if (body->description) { + format_editorial(body->description,ps_global->ttyo->screen_cols,pc); + gf_puts(NEWLINE,pc); + result = 1; + } + for (part=body->nested.part; part; part=part->next) { + result |= describe_smime_bodies(&(part->body),pc); + } + + } + } else if (body->type == TYPEMESSAGE && + body->subtype && strucmp(body->subtype, "rfc822")==0) { + result |= describe_smime_bodies(body->nested.msg->body,pc); + } + + return result; + } + #endif /*---------------------------------------------------------------------- Add lines to the attachments structure *************** *** 1677,1682 **** --- 1738,1751 ---- show_parts = 0; + #ifdef SMIME + if (flgs & FM_DISPLAY) { + if (describe_smime_bodies(body,pc)) { + gf_puts(NEWLINE, pc); + } + } + #endif + /*======== Now loop through formatting all the parts =======*/ for(a = ps_global->atmts; a->description != NULL; a++) { *************** *** 6150,6155 **** --- 6219,6236 ---- t = &tmp_20k_buf[strlen(tmp_20k_buf)]; + #ifdef SMIME + if (is_pkcs7_body(body) && type!=3) { /* if smime and not attempting print */ + + sstrcpy(&t,"\015\012"); + + sstrcpy(&t, + "This part is a PKCS7 S/MIME enclosure. " + "You may be able to view it by entering the correct passphrase " + "with the \"^D\" command. Press \"^E\" for more information."); + + } else + #endif if(type){ sstrcpy(&t, "\015\012"); switch(type) { diff -Ncr pine4.53/pine/makefile.lnx pine4.53-smime/pine/makefile.lnx *** pine4.53/pine/makefile.lnx Tue Sep 10 14:34:39 2002 --- pine4.53-smime/pine/makefile.lnx Wed Jan 15 12:48:15 2003 *************** *** 60,79 **** LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ mailview.o other.o pine.o strings.o takeaddr.o STDLIBS= -lncurses LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ `cat $(CCLIENTDIR)/LDFLAGS` STDCFLAGS= -DLNX -DSYSTYPE=\"LNX\" -DMOUSE CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ $(STDCFLAGS) OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ ! os.o HFILES= headers.h os.h pine.h context.h helptext.h \ $(PICODIR)/headers.h $(PICODIR)/estruct.h \ --- 60,92 ---- LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ mailview.o other.o pine.o strings.o takeaddr.o + SSLDIR= $(HOME)/local/ssl + SSLCERTS= $(SSLDIR)/certs + SSLINCLUDE= $(SSLDIR)/include + SSLLIB= $(SSLDIR)/lib + + SSLCFLAGS= -I$(SSLINCLUDE) \ + -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" \ + -DSMIME + SSLLDFLAGS= -L$(SSLLIB) -lcrypto + + STDLIBS= -lncurses LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ + $(SSLLDFLAGS) \ `cat $(CCLIENTDIR)/LDFLAGS` STDCFLAGS= -DLNX -DSYSTYPE=\"LNX\" -DMOUSE CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ + $(SSLCFLAGS) \ $(STDCFLAGS) OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ ! os.o bss_so.o smime.o smkeys.o HFILES= headers.h os.h pine.h context.h helptext.h \ $(PICODIR)/headers.h $(PICODIR)/estruct.h \ *************** *** 135,137 **** --- 148,154 ---- osdep/sendmail osdep/execview \ osdep/postreap.wtp osdep/os-lnx.ic cd osdep; $(MAKE) includer os-lnx.c; cd .. + + jon.o: jon.c + $(CC) $(CFLAGS) -Wall -Wstrict-prototypes -c $< -o $@ + diff -Ncr pine4.53/pine/makefile.so5 pine4.53-smime/pine/makefile.so5 *** pine4.53/pine/makefile.so5 Tue Oct 23 15:24:51 2001 --- pine4.53-smime/pine/makefile.so5 Wed Jan 15 12:48:15 2003 *************** *** 62,67 **** --- 62,78 ---- LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ mailview.o other.o pine.o strings.o takeaddr.o + SSLDIR= $(HOME)/local/ssl + SSLCERTS= $(SSLDIR)/certs + SSLINCLUDE= $(SSLDIR)/include + SSLLIB= $(SSLDIR)/lib + + SSLCFLAGS= -I$(SSLINCLUDE) \ + -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" \ + -DSMIME + SSLLDFLAGS= -L$(SSLLIB) -lcrypto + + # LDCC= /usr/bin/cc # If you don't have /usr/bin/cc (our Solaris 2.2 doesn't seem to have it, # it only has /usr/ucb/cc) then change LDCC to the following line and *************** *** 72,88 **** STDLIBS= -ltermlib LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ `cat $(CCLIENTDIR)/LDFLAGS` STDCFLAGS= -Dconst= -DSV4 -DSYSTYPE=\"SOL\" -DMOUSE CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ $(STDCFLAGS) OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ ! os.o HFILES= headers.h os.h pine.h context.h helptext.h \ $(PICODIR)/headers.h $(PICODIR)/estruct.h \ --- 83,101 ---- STDLIBS= -ltermlib LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ + $(SSLLDFLAGS) \ `cat $(CCLIENTDIR)/LDFLAGS` STDCFLAGS= -Dconst= -DSV4 -DSYSTYPE=\"SOL\" -DMOUSE CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ + $(SSLCFLAGS) \ $(STDCFLAGS) OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ ! os.o bss_so.o smime.o smkeys.o HFILES= headers.h os.h pine.h context.h helptext.h \ $(PICODIR)/headers.h $(PICODIR)/estruct.h \ diff -Ncr pine4.53/pine/pine.h pine4.53-smime/pine/pine.h *** pine4.53/pine/pine.h Fri Jan 10 15:25:55 2003 --- pine4.53-smime/pine/pine.h Wed Jan 15 12:48:15 2003 *************** *** 1134,1139 **** --- 1134,1144 ---- F_DISABLE_SHARED_NAMESPACES, F_EXPOSE_HIDDEN_CONFIG, F_ALT_COMPOSE_MENU, + #ifdef SMIME + F_SIGN_DEFAULT_ON, + F_ENCRYPT_DEFAULT_ON, + F_REMEMBER_SMIME_PASSPHRASE, + #endif F_ALWAYS_SPELL_CHECK, F_QUELL_TIMEZONE, F_COLOR_LINE_IMPORTANT, *************** *** 2031,2036 **** --- 2036,2043 ---- } cmdline_val; /* user typed as cmdline arg */ }; + #define MC_DECRYPT 800 + #define MC_SECURITY 801 /* *************** *** 4368,4373 **** --- 4375,4383 ---- char *pine_send_status PROTO((int, char *, char *, int *)); void phone_home PROTO((char *)); void pine_free_body PROTO((BODY **)); + int pine_write_body_header PROTO((BODY *, soutr_t, TCPSTREAM *)); + long pine_rfc822_output_body PROTO((BODY *,soutr_t,TCPSTREAM *)); + void pine_encode_body PROTO((BODY *)); void simple_header_parse PROTO((char *, char **, char **)); int valid_subject PROTO((char *, char **, char **,BUILDER_ARG *,int *)); long new_mail_for_pico PROTO((int, int)); diff -Ncr pine4.53/pine/pine.hlp pine4.53-smime/pine/pine.hlp *** pine4.53/pine/pine.hlp Wed Jan 15 11:55:09 2003 --- pine4.53-smime/pine/pine.hlp Wed Jan 15 12:48:15 2003 *************** *** 24338,24340 **** --- 24338,24347 ---- ========== h_select_by_smaller_size ========== Enter a number or ^C to cancel. All messages less than this many characters in size will be selected. Examples: 2176, 1.53K (1530), or 3M (3000000). + ========== h_config_sign_default_on ========== + If enabled, the 'Sign' option will default to on when sending messages. + ========== h_config_encrypt_default_on ========== + If enabled, the 'Encrypt' option will default to on when sending messages. + ========== h_config_remember_smime_passphrase ========== + If enabled, you will only have to enter your passphrase for your private key + once during a pine session. diff -Ncr pine4.53/pine/send.c pine4.53-smime/pine/send.c *** pine4.53/pine/send.c Tue Jan 14 13:22:59 2003 --- pine4.53-smime/pine/send.c Wed Jan 15 12:48:15 2003 *************** *** 50,55 **** --- 50,56 ---- #include "../c-client/smtp.h" #include "../c-client/nntp.h" + #include "smime.h" #ifndef TCPSTREAM #define TCPSTREAM void *************** *** 5490,5495 **** --- 5491,5513 ---- opts[i++].label = ""; } + #ifdef SMIME + { + opts[i].ch = 'e'; + opts[i].rval = 'e'; + opts[i].name = "E"; + opts[i++].label = "Encrypt"; + + opts[i].ch = 'g'; + opts[i].rval = 'g'; + opts[i].name = "G"; + opts[i++].label = "Sign"; + + g_do_encrypt = F_ON(F_ENCRYPT_DEFAULT_ON,ps_global); + g_do_sign = F_ON(F_SIGN_DEFAULT_ON,ps_global); + } + #endif + opts[i].ch = -1; no_help = (i >= 12); *************** *** 5574,5579 **** --- 5592,5627 ---- sstrcpy(&optp, dsn_string); } + #ifdef SMIME + if (g_do_encrypt) { + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + + sstrcpy(&optp, "Encrypted"); + } + + if (g_do_sign) { + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + + sstrcpy(&optp, "Signed"); + } + #endif + if(lparen) *optp++ = ')'; *************** *** 5675,5680 **** --- 5723,5734 ---- * body on failure. */ dsn_requested = (DSN_SHOW | DSN_SUCCESS | DSN_DELAY | DSN_FULL); + #ifdef SMIME + } else if (rv=='e') { + g_do_encrypt = !g_do_encrypt; + } else if (rv=='g') { + g_do_sign = !g_do_sign; + #endif } sprintf(dsn_string, "DSN requested[%s%s%s%s]", *************** *** 6418,6423 **** --- 6472,6478 ---- char *verbose_file = NULL; BODY *bp = NULL; PINEFIELD *pf; + BODY *origBody = body; #define MAX_ADDR_ERROR 2 /* Only display 2 address errors */ *************** *** 6434,6439 **** --- 6489,6518 ---- return(0); } + + #ifdef SMIME + if (g_do_encrypt || g_do_sign) { + int result; + + STORE_S *so = lmc.so; + lmc.so = NULL; + + result = 1; + + if (g_do_encrypt) + result = encrypt_outgoing_message(header,&body); + + /* need to free new body from encrypt if sign fails? */ + if (result && g_do_sign) + result = sign_outgoing_message(header,&body,g_do_encrypt); + + lmc.so = so; + + if (!result) + return 0; + } + #endif + /* set up counts and such to keep track sent percentage */ send_bytes_sent = 0; gf_filter_init(); /* zero piped byte count, 'n */ *************** *** 6742,6747 **** --- 6821,6844 ---- mail_free_envelope(&fake_env); done: + + #ifdef SMIME + /* Free replacement encrypted body */ + if (body != origBody) { + + if (body->type==TYPEMULTIPART) { + /* Just get rid of first part, it's actually origBody */ + void *x = body->nested.part; + + body->nested.part = body->nested.part->next; + + fs_give(&x); + } + + pine_free_body(&body); + } + #endif + if(we_cancel) cancel_busy_alarm(0); *************** *** 8721,8733 **** dprint(4, (debugfile, "-- pine_encode_body: %d\n", body ? body->type : 0)); if (body) switch (body->type) { case TYPEMULTIPART: /* multi-part */ ! if (!body->parameter) { /* cookie not set up yet? */ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0), getpid ()); ! body->parameter = mail_newbody_parameter (); ! body->parameter->attribute = cpystr ("BOUNDARY"); ! body->parameter->value = cpystr (tmp); } part = body->nested.part; /* encode body parts */ do pine_encode_body (&part->body); --- 8818,8834 ---- dprint(4, (debugfile, "-- pine_encode_body: %d\n", body ? body->type : 0)); if (body) switch (body->type) { case TYPEMULTIPART: /* multi-part */ ! if (!body->parameter || strucmp(body->parameter->attribute,"BOUNDARY")!=0) { /* cookie not set up yet? */ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ + PARAMETER *param; + sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0), getpid ()); ! param = mail_newbody_parameter (); ! param->next = body->parameter; ! param->attribute = cpystr ("BOUNDARY"); ! param->value = cpystr (tmp); ! body->parameter = param; } part = body->nested.part; /* encode body parts */ do pine_encode_body (&part->body); diff -Ncr pine4.53/pine/smime.c pine4.53-smime/pine/smime.c *** pine4.53/pine/smime.c Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/smime.c Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,1776 ---- + /* + File: smime.c + Author: paisleyj@dcs.gla.ac.uk + Date: 01/2001 + + Description: + This file contains all the low-level functions + required for dealing with S/MIME objects. + + References are made to the functions in this file + from the following locations: + + mailview.c:part_desc() -> is_pkcs7_body() + send.c:call_mailer() -> encrypt_outgoing_message() + send.c:call_mailer() -> sign_outgoing_message() + mailcmd.c:process_cmd() -> get_passphrase() + mailcmd.c:process_cmd() -> smime_info_screen() + */ + + #ifdef SMIME + + #include "headers.h" + + #include + #include + #include + #include + + #include + #include + #include + + #include + #include + #include + #include + + #include "bss_so.h" + #include "smkeys.h" + #include "smime.h" + + #define PINE_SMIME_DIRNAME ".pine-smime" + + /* Set true if loading a key failed due to lack of passphrase. + Queried in mailcmd.c:process_cmd() before calling get_passphrase() + */ + int g_need_passphrase = 0; + /* User has entered a passphrase */ + static int s_entered_passphrase = 0; + /* Storage for the entered passphrase */ + static char s_passphrase[80]; + static char *s_passphrase_emailaddr; + + /* Set true if encrypting/signing (respectively) + Referenced from send.c:call_mailer() and send.c:send_exit_for_pico + */ + int g_do_encrypt; + int g_do_sign; + + /* Full pathname to ~/.pine-smime */ + static char *g_pine_smime_dir; + + static BIO *bio_err; + + /* Linked list of PERSONAL_CERT objects */ + static PERSONAL_CERT *s_personal_certs; + + static X509_STORE *s_cert_store; + + /* State management for randomness functions below */ + static int seeded = 0; + static int egdsocket = 0; + + /* Forget any cached private keys */ + static void forget_private_keys() + { + PERSONAL_CERT *pcert; + + for (pcert=s_personal_certs; pcert; pcert=pcert->next) { + + if (pcert->key) { + EVP_PKEY_free(pcert->key); + pcert->key = NULL; + } + } + } + + /* taken from openssl/apps/app_rand.c */ + static int app_RAND_load_file(const char *file, BIO *bio_e, int dont_warn) + { + int consider_randfile = (file == NULL); + char buffer[200]; + + if (file == NULL) + file = RAND_file_name(buffer, sizeof buffer); + else if (RAND_egd(file) > 0) + { + /* we try if the given filename is an EGD socket. + if it is, we don't write anything back to the file. */ + egdsocket = 1; + return 1; + } + if (file == NULL || !RAND_load_file(file, -1)) + { + if (RAND_status() == 0 && !dont_warn) + { + BIO_printf(bio_e,"unable to load 'random state'\n"); + BIO_printf(bio_e,"This means that the random number generator has not been seeded\n"); + BIO_printf(bio_e,"with much random data.\n"); + if (consider_randfile) /* explanation does not apply when a file is explicitly named */ + { + BIO_printf(bio_e,"Consider setting the RANDFILE environment variable to point at a file that\n"); + BIO_printf(bio_e,"'random' data can be kept in (the file will be overwritten).\n"); + } + } + return 0; + } + seeded = 1; + return 1; + } + + /* copied and fiddled from pine/imap/src/osdep/unix/auth_ssl.c */ + static void openssl_extra_randomness(void) + { + #if !defined(WIN32) + int fd; + unsigned long i; + char tmp[MAXPATH]; + struct stat sbuf; + /* if system doesn't have /dev/urandom */ + if (stat ("/dev/urandom",&sbuf)) { + if ((fd = open (tmpnam (tmp),O_WRONLY|O_CREAT,0600)) < 0) + i = (unsigned long) tmp; + else { + unlink (tmp); /* don't need the file */ + fstat (fd,&sbuf); /* get information about the file */ + i = sbuf.st_ino; /* remember its inode */ + close (fd); /* or its descriptor */ + } + /* not great but it'll have to do */ + sprintf (tmp + strlen (tmp),"%.80s%lx%lx%lx", + tcp_serverhost (),i, + (unsigned long) (time (0) ^ gethostid ()), + (unsigned long) getpid ()); + RAND_seed (tmp,strlen (tmp)); + } + #endif + } + + /* taken from openssl/apps/app_rand.c */ + static int app_RAND_write_file(const char *file, BIO *bio_e) + { + char buffer[200]; + + if (egdsocket || !seeded) + /* If we did not manage to read the seed file, + * we should not write a low-entropy seed file back -- + * it would suppress a crucial warning the next time + * we want to use it. */ + return 0; + + if (file == NULL) + file = RAND_file_name(buffer, sizeof buffer); + if (file == NULL || !RAND_write_file(file)) + { + BIO_printf(bio_e,"unable to write 'random state'\n"); + return 0; + } + return 1; + } + + /* Installed as an atexit() handler to save the random data */ + static void openssl_deinit(void) + { + app_RAND_write_file(NULL, bio_err); + } + + /* Initialise openssl stuff if needed */ + static void openssl_init(void) + { + static int inited = 0; + + if (!inited) { + + char *p; + char buf[MAXPATH]; + + /* Find where that .pine-smime thing is */ + /* Perhaps we should just use the user's home directory as a start? */ + p = last_cmpnt(ps_global->pinerc); + buf[0] = '\0'; + if(p != NULL) { + strncpy(buf, ps_global->pinerc, min(p - ps_global->pinerc, sizeof(buf)-1)); + buf[min(p - ps_global->pinerc, sizeof(buf)-1)] = '\0'; + } + + strncat(buf, PINE_SMIME_DIRNAME, sizeof(buf)-1-strlen(buf)); + buf[sizeof(buf)-1] = 0; + + if (can_access(buf, ACCESS_EXISTS)==0) { + + g_pine_smime_dir = cpystr(buf); + s_cert_store = get_ca_store(g_pine_smime_dir); + s_personal_certs = get_personal_certs(g_pine_smime_dir); + + } else g_pine_smime_dir = ""; /* prevent null dereference later */ + + SSLeay_add_all_algorithms(); + ERR_load_crypto_strings(); + + /* this won't make any sense (since the terminal's in a funny mode) */ + if ((bio_err=BIO_new(BIO_s_file())) != NULL) + BIO_set_fp(bio_err,stderr,BIO_NOCLOSE|BIO_FP_TEXT); + + app_RAND_load_file(NULL, bio_err, 1); + + openssl_extra_randomness(); + + /* save the rand file when we're done */ + atexit(openssl_deinit); + + inited = 1; + } + + ERR_clear_error(); + } + + /* Get a pointer to a string describing the most recent OpenSSL error. + It's statically allocated, so don't change or attempt to free it. + */ + static const char *openssl_error_string(void) + { + char *errs; + const char *data = NULL; + long errn; + + errn = ERR_peek_error_line_data(NULL, NULL, &data, NULL); + errs = (char*)ERR_reason_error_string(ERR_GET_REASON(errn)); + + if (errs) + return errs; + else if (data) + return data; + + return "unknown error"; + } + + /* Return true if the body looks like a PKCS7 object */ + int is_pkcs7_body(BODY *body) + { + int result; + + result = body->type==TYPEAPPLICATION && + body->subtype && + (strucmp(body->subtype,"pkcs7-mime")==0 || + strucmp(body->subtype,"x-pkcs7-mime")==0 || + strucmp(body->subtype,"pkcs7-signature")==0 || + strucmp(body->subtype,"x-pkcs7-signature")==0); + + return result; + } + + /* debug utility to dump the contents of a BIO to a file */ + static void dump_bio_to_file(BIO *in,char *filename) + { + char iobuf[4096]; + int len; + BIO *out; + + out = BIO_new_file(filename,"w"); + + if (out) { + BIO_reset(in); + + while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) + BIO_write(out, iobuf, len); + BIO_free(out); + } + } + + /* prompt the user for their passphrase + (possibly prompting with the email address in s_passphrase_emailaddr) + */ + int get_passphrase(void) + { + int rc; + int flags; + char prompt[50]; + HelpType help = NO_HELP; + + sprintf(prompt, + "Enter passphrase for <%s>: ",s_passphrase_emailaddr ? s_passphrase_emailaddr : "unknown"); + + do { + flags = OE_PASSWD | OE_DISALLOW_HELP; + rc = optionally_enter(s_passphrase, -FOOTER_ROWS(ps_global), 0, sizeof(s_passphrase), + prompt, NULL, help, &flags); + } while (rc!=0 && rc!=1 && rc>0); + + if (rc==0) + s_entered_passphrase = 1; + + return rc==0; + } + + /* Recursively stash a pointer to the decrypted data in our + manufactured body. + */ + static void create_local_cache(char *base,BODY *b) + { + if (b->type==TYPEMULTIPART) { + PART *p; + + #if 0 + cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); + #else + /* don't really want to copy the real body contents. It shouldn't be + used, and in the case of a message with attachments, we'll be + duplicating the files multiple times + */ + cpytxt(&b->contents.text, "BODY UNAVAILABLE", 16); + #endif + + for (p=b->nested.part;p;p=p->next) { + create_local_cache(base,(BODY*) p); + } + } else { + cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); + } + } + + static long rfc822_output_func(void *stream,char *string) + { + STORE_S *so = (STORE_S*) stream; + + return so_puts(so,string)!=0; + } + + /* Load a private key from the given file */ + static EVP_PKEY *load_key(char *file, char *pass) + { + BIO *in; + EVP_PKEY *key; + if(!(in = BIO_new_file(file, "r"))) return NULL; + key = PEM_read_bio_PrivateKey(in, NULL,NULL,pass); + BIO_free(in); + return key; + } + + /* Attempt to load the private key for the given PERSONAL_CERT. + This sets the appropriate passphrase globals in order to + interact with the user correctly. + */ + static int load_private_key(PERSONAL_CERT *pcert) + { + if (!pcert->key) { + + /* Try empty password by default */ + char *password = ""; + + if (g_need_passphrase) { + /* We've already been in here and discovered we need a different password */ + + if (s_entered_passphrase) + password = s_passphrase; /* Use the passphrase if it's been entered */ + else return 0; + } + + ERR_clear_error(); + + if(!(pcert->key = load_key(pcert->file, password))) { + long err = ERR_get_error(); + + /* Couldn't load key... */ + + if (s_entered_passphrase) { + + /* The user got the password wrong maybe? */ + + if ((ERR_GET_LIB(err)==ERR_LIB_EVP && ERR_GET_REASON(err)==EVP_R_BAD_DECRYPT) || + (ERR_GET_LIB(err)==ERR_LIB_PEM && ERR_GET_REASON(err)==PEM_R_BAD_DECRYPT)) + q_status_message(SM_ORDER | SM_DING,1,1,"Wrong password"); + else q_status_message1(SM_ORDER,1,1,"Couldn't read key: %s",(char*)openssl_error_string()); + + /* This passphrase is no good; forget it */ + s_entered_passphrase = 0; + } + + /* Indicate to the UI that we need re-entry (see mailcmd.c:process_cmd())*/ + g_need_passphrase = 1; + + fs_give((void**) &s_passphrase_emailaddr); + s_passphrase_emailaddr = get_x509_subject_email(pcert->cert); + return 0; + } else { + /* This key will be cached, so we won't be called again */ + s_entered_passphrase = 0; + g_need_passphrase = 0; + } + + return 1; + } + + return 0; + } + + static void setup_pkcs7_body_for_signature(BODY *b,char *description,char *type,char *filename) + { + b->type = TYPEAPPLICATION; + b->subtype = cpystr(type); + b->encoding = ENCBINARY; + + b->description = cpystr(description); + + b->disposition.type = cpystr("attachment"); + b->disposition.parameter = mail_newbody_parameter(); + b->disposition.parameter->attribute = cpystr("filename"); + b->disposition.parameter->value = cpystr(filename); + + b->parameter = mail_newbody_parameter(); + b->parameter->attribute = cpystr("name"); + b->parameter->value = cpystr(filename); + } + + /* + Look for a personal certificate matching the + given address + */ + PERSONAL_CERT *match_personal_cert_to_email(ADDRESS *a) + { + PERSONAL_CERT *pcert; + char buf[MAXPATH]; + char *email; + + if (!a || !a->mailbox || !a->host) + return NULL; + + snprintf(buf,sizeof(buf),"%s@%s",a->mailbox,a->host); + + for (pcert=s_personal_certs;pcert;pcert=pcert->next) { + + if (!pcert->cert) + continue; + + email = get_x509_subject_email(pcert->cert); + + if (email && strucmp(email,buf)==0) { + fs_give((void**) &email); + break; + } + + fs_give((void**) &email); + } + + return pcert; + } + + /* + Look for a personal certificate matching the from + (or reply_to? in the given envelope) + */ + PERSONAL_CERT *match_personal_cert(ENVELOPE *env) + { + PERSONAL_CERT *pcert; + + pcert = match_personal_cert_to_email(env->reply_to); + if (!pcert) + pcert = match_personal_cert_to_email(env->from); + + return pcert; + } + + /* + Flatten the given body into its MIME representation. + Return the result in a CharStar STORE_S. + */ + static STORE_S *body_to_store(BODY *body) + { + STORE_S *store; + store = so_get(CharStar, NULL, EDIT_ACCESS); + if (!store) + return NULL; + + pine_encode_body(body); /* this attaches random boundary strings to multiparts */ + pine_write_body_header (body, rfc822_output_func,store); + pine_rfc822_output_body(body, rfc822_output_func,store); + + /* now need to truncate by two characters since the above + appends CRLF to the stream + */ + + /** Eek! No way of telling size of a STORE_S. We depend on knowing it's + a CharStar */ + so_truncate(store,((char*)store->eod-(char*)store->txt)-2); + + so_seek(store,0,SEEK_SET); + + return store; + } + + + + /* + Sign a message. Called from call_mailer in send.c. + + This takes the header for the outgoing message as well as a pointer + to the current body (which may be reallocated). + */ + int sign_outgoing_message(METAENV *header,BODY **bodyP,int dont_detach) + { + STORE_S *store = NULL; + STORE_S *outs = NULL; + BODY *body = *bodyP; + BODY *newBody = NULL; + PART *p1 = NULL; + PART *p2 = NULL; + PERSONAL_CERT *pcert; + BIO *in = NULL; + BIO *out = NULL; + PKCS7 *p7 = NULL; + int result = 0; + PARAMETER *param; + + int flags = dont_detach ? 0 : PKCS7_DETACHED; + + openssl_init(); + + store = body_to_store(body); + + /* Look for a private key matching the sender address... */ + + pcert = match_personal_cert(header->env); + + if (!pcert) { + q_status_message(SM_ORDER,1,1,"Couldn't find the certificate needed to sign."); + goto end; + } + + if (!load_private_key(pcert) && g_need_passphrase) { + /* Couldn't load key with blank password, try again */ + get_passphrase(); + load_private_key(pcert); + } + + if (!pcert->key) + goto end; + + in = BIO_new_so(store); + + #if 0 + dump_bio_to_file(in,"/tmp/signed-data"); + #endif + + BIO_reset(in); + + p7 = PKCS7_sign(pcert->cert, pcert->key, NULL, in, flags); + if (!p7) { + q_status_message(SM_ORDER,1,1,"Error creating PKCS7 object."); + goto end; + } + + outs = so_get(CharStar,NULL,EDIT_ACCESS); + out = BIO_new_so(outs); + + i2d_PKCS7_bio(out, p7); + BIO_flush(out); + + so_seek(outs,0,SEEK_SET); + + if ((flags&PKCS7_DETACHED)==0) { + + /* the simple case: the signed data is in the pkcs7 object */ + + newBody = mail_newbody(); + + setup_pkcs7_body_for_signature(newBody,"S/MIME Cryptographically Signed Message","x-pkcs7-mime","smime.p7m"); + + newBody->contents.text.data = (char*) outs; + *bodyP = newBody; + + result = 1; + } else { + + /* OK. + We have to create a new body as follows: + + multipart/signed; blah blah blah + reference to existing body + + pkcs7 object + */ + + newBody = mail_newbody(); + + newBody->type = TYPEMULTIPART; + newBody->subtype = cpystr("signed"); + newBody->encoding = ENC7BIT; + + newBody->parameter = param = mail_newbody_parameter(); + param->attribute = cpystr("protocol"); + param->value = cpystr("application/x-pkcs7-signature"); + + newBody->parameter->next = param = mail_newbody_parameter(); + param->attribute = cpystr("micalg"); + param->value = cpystr("sha1"); + + p1 = mail_newbody_part(); + p2 = mail_newbody_part(); + + /* this is nasty. We're just copying the body in here, + but since our newBody is freed at the end of call_mailer, + we mustn't let this body (the original one) be freed twice. + */ + p1->body = *body; /* ARRGH. This is special cased at the end of call_mailer */ + + p1->next = p2; + + setup_pkcs7_body_for_signature(&p2->body,"S/MIME Cryptographic Signature","x-pkcs7-signature","smime.p7s"); + p2->body.contents.text.data = (char*) outs; + + newBody->nested.part = p1; + + *bodyP = newBody; + + result = 1; + } + + end: + + PKCS7_free(p7); + BIO_free(in); + BIO_free(out); + if (store) + so_give(&store); + + return result; + } + + /* + Encrypt a message on the way out. Called from call_mailer in send.c + The body may be reallocated. + */ + int encrypt_outgoing_message(METAENV *header,BODY **bodyP) + { + PKCS7 *p7 = NULL; + BIO *in = NULL; + BIO *out = NULL; + EVP_CIPHER *cipher = NULL; + STACK_OF(X509) *encerts = NULL; + STORE_S *store = NULL; + STORE_S *outs = NULL; + PINEFIELD *pf; + ADDRESS *a; + BODY *body = *bodyP; + BODY *newBody = NULL; + int result = 0; + + openssl_init(); + + cipher = EVP_des_cbc(); + + encerts = sk_X509_new_null(); + + /* Look for a certificate for each of the recipients */ + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr) { + + for (a=*pf->addr;a;a=a->next) { + X509 *cert; + char buf[MAXPATH]; + + snprintf(buf,sizeof(buf),"%s@%s",a->mailbox,a->host); + + cert = get_cert_for(g_pine_smime_dir,buf); + if (cert) + sk_X509_push(encerts,cert); + else { + q_status_message2(SM_ORDER,1,1, + "Unable to find certificate for <%s@%s>",a->mailbox,a->host); + goto end; + } + } + } + + + store = body_to_store(body); + + in = BIO_new_so(store); + + p7 = PKCS7_encrypt(encerts, in, cipher, 0); + + outs = so_get(CharStar,NULL,EDIT_ACCESS); + out = BIO_new_so(outs); + + i2d_PKCS7_bio(out, p7); + BIO_flush(out); + so_seek(outs,0,SEEK_SET); + + newBody = mail_newbody(); + + newBody->type = TYPEAPPLICATION; + newBody->subtype = cpystr("x-pkcs7-mime"); + newBody->encoding = ENCBINARY; + + newBody->description = cpystr("S/MIME Encrypted Message"); + + newBody->contents.text.data = (char*) outs; + + *bodyP = newBody; + + result = 1; + + end: + + BIO_free(in); + BIO_free(out); + PKCS7_free(p7); + sk_X509_pop_free(encerts, X509_free); + if (store) + so_give(&store); + + return result; + } + + /* + Plonk the contents (mime headers and body) of the given + section of a message to a CharStar STORE_S object. + */ + static STORE_S *get_raw_part(int msgno,const char *section) + { + long len; + STORE_S *store = NULL; + char *text; + + store = so_get(CharStar, NULL, EDIT_ACCESS); + if (store) { + + /* First grab headers of the chap */ + text = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) section, &len, 0); + + if (text) { + so_nputs(store,text,len); + + /** Now grab actual body */ + text = mail_fetch_body (ps_global->mail_stream, msgno, (char*) section, &len, 0); + if (text) { + so_nputs(store,text,len); + + so_seek(store,0,SEEK_SET); + + } else so_give(&store); + + } else so_give(&store); + + } + return store; + } + + /* + Get (and decode) the body of the given section of msg + */ + static STORE_S *get_part_contents(int msgno,const char *section) + { + long len; + gf_io_t pc; + STORE_S *store = NULL; + char *err; + + store = so_get(CharStar, NULL, EDIT_ACCESS); + if (store) { + gf_set_so_writec(&pc,store); + + err = detach(ps_global->mail_stream, msgno, (char*) section,&len, pc, NULL); + + gf_clear_so_writec(store); + + so_seek(store,0,SEEK_SET); + + if (err) + so_give(&store); + } + return store; + } + + static PKCS7 *get_pkcs7_from_part(int msgno,const char *section) + { + STORE_S *store = NULL; + PKCS7 *p7 = NULL; + BIO *in = NULL; + + store = get_part_contents(msgno,section); + + if (store) { + in = BIO_new_so(store); + if (in) { + p7=d2i_PKCS7_bio(in,NULL); + } + } + + if (store) + so_give(&store); + + BIO_free(in); + + return p7; + } + + /* + Try to verify a signature. + + p7 - the pkcs7 object to verify + in - the plain data to verify (NULL if not detached) + out - BIO to which to write the opaque data + */ + static int do_signature_verify(PKCS7 *p7,BIO *in,BIO *out) + { + STACK_OF(X509) *otherCerts = NULL; + int result; + const char *data; + long err; + + #if 0 + dump_bio_to_file(in,"/tmp/verified-data"); + #endif + + BIO_reset(in); + + #if 0 + /* testing verification stuff */ + { + X509 *c; + + c = get_cert_for(g_pine_smime_dir,"xx"); + if (c) { + X509_add1_reject_object(c, OBJ_nid2obj(NID_email_protect)); + + X509_STORE_add_cert(s_cert_store,c); + + save_cert_for(g_pine_smime_dir,cpystr("yy"),c); + } + + + ERR_clear_error(); + + } + #endif + + result = PKCS7_verify(p7, otherCerts, s_cert_store, + in, out, 0); + + if (result) { + q_status_message(SM_ORDER,1,1,"S/MIME signature verified ok"); + } else { + err = ERR_peek_error_line_data(NULL, NULL, &data, NULL); + + if (out && err==ERR_PACK(ERR_LIB_PKCS7,PKCS7_F_PKCS7_VERIFY,PKCS7_R_CERTIFICATE_VERIFY_ERROR)) { + + /* Retry verification so we can get the plain text */ + /* Might be better to reimplement PKCS7_verify here? */ + + PKCS7_verify(p7, otherCerts, s_cert_store, + in, out, PKCS7_NOVERIFY); + + } + + q_status_message1(SM_ORDER | SM_DING,1,1,"Couldn't verify S/MIME signature: %s",(char*) openssl_error_string()); + + return result; + } + + /* now try to extract the certificates of any signers */ + { + STACK_OF(X509) *signers; + int i; + + signers = PKCS7_get0_signers(p7, NULL, 0); + + if (signers) + for (i=0;isparep) { + PKCS7_free((PKCS7*) b->sparep); + b->sparep = NULL; + } + } + + /* + Given a multipart body of type multipart/signed, attempt to verify + it + */ + static int do_detached_signature_verify(BODY *b,int msgno,char *section) + { + STORE_S *toVerify = NULL; + PKCS7 *p7 = NULL; + BIO *in = NULL; + PART *p; + int result = 0; + char seq[100]; + char *what_we_did; + + openssl_init(); + + snprintf(seq,sizeof(seq),"%s1",section); + toVerify = get_raw_part(msgno,seq); + + if (toVerify) { + + in = BIO_new_so(toVerify); + if (!in) + goto end; + + snprintf(seq,sizeof(seq),"%s2",section); + p7 = get_pkcs7_from_part(msgno,seq); + + if (!p7) + goto end; + + result = do_signature_verify(p7,in,NULL); + + if (b->subtype) fs_give((void**) &b->subtype); + b->subtype = cpystr("x-pkcs7-enclosure"); + b->encoding = ENC8BIT; + + if (b->description) fs_give ((void**) &b->description); + + what_we_did = result ? "This message was cryptographically signed." : + "This message was cryptographically signed but the signature could not be verified."; + + b->description = cpystr(what_we_did); + + b->sparep = p7; + p7 = NULL; + b->cleanup = smime_body_cleanup; + + p = b->nested.part; + + /* p is signed plaintext */ + if (p && p->next) + mail_free_body_part(&p->next); /* hide the pkcs7 from the viewer */ + + result = 0; + } + end: + BIO_free(in); + + PKCS7_free(p7); + + if (toVerify) + so_give(&toVerify); + + return result; + } + + static PERSONAL_CERT *find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri) + { + PERSONAL_CERT *x; + + for (x=s_personal_certs;x;x=x->next) { + X509 *mine; + + mine = x->cert; + + if (!X509_NAME_cmp(ri->issuer_and_serial->issuer,mine->cert_info->issuer) && + !ASN1_INTEGER_cmp(ri->issuer_and_serial->serial,mine->cert_info->serialNumber)) { + break; + } + } + + return x; + } + + static PERSONAL_CERT *find_certificate_matching_pkcs7(PKCS7 *p7) + { + int i; + STACK_OF(PKCS7_RECIP_INFO) *recips; + PERSONAL_CERT *x = NULL; + + recips = p7->d.enveloped->recipientinfo; + + for (i=0; isparep) { + + p7 = (PKCS7*) b->sparep; + + } else { + + p7 = get_pkcs7_from_part(msgno,section); + if (p7 == NULL) { + q_status_message1(SM_ORDER,1,1,"Couldn't load PKCS7 object: %s",(char*)openssl_error_string()); + goto end; + } + + /* Save the PKCS7 object for later dealings by the user interface. + It will be cleaned up when the body is garbage collected + */ + b->sparep = p7; + b->cleanup = smime_body_cleanup; + } + + if (PKCS7_type_is_signed(p7)) { + int sigok; + + outs = so_get(CharStar, NULL, EDIT_ACCESS); + so_puts(outs,"MIME-Version: 1.0\r\n"); /* needed so rfc822_parse_msg_full believes it's MIME */ + out = BIO_new_so(outs); + + sigok = do_signature_verify(p7,NULL,out); + + /* shouldn't really duplicate these messages */ + what_we_did = sigok ? "This message was cryptographically signed." : + "This message was cryptographically signed but the signature could not be verified."; + + } else if (!PKCS7_type_is_enveloped(p7)) { + q_status_message(SM_ORDER,1,1,"PKCS7 object not recognised."); + goto end; + } else { /* It *is* enveloped */ + + what_we_did = "This message was encrypted."; + + /* + Now need to find a cert that can decrypt this boy + */ + pcert = find_certificate_matching_pkcs7(p7); + + if (!pcert) { + q_status_message(SM_ORDER,1,1,"Couldn't find the certificate needed to decrypt."); + goto end; + } + + recip = pcert->cert; + + load_private_key(pcert); + + key = pcert->key; + if (!key) + goto end; + + outs = so_get(CharStar, NULL, EDIT_ACCESS); + so_puts(outs,"MIME-Version: 1.0\r\n"); + + out = BIO_new_so(outs); + + if(!PKCS7_decrypt(p7, key, recip, out, 0)) { + q_status_message1(SM_ORDER,1,1,"Error decrypting PKCS7: %s",(char*) openssl_error_string()); + goto end; + } + + } + + /* We've now produced a flattened MIME object in store outs. + It needs to be turned back into a BODY + */ + + { + BODY *body; + ENVELOPE *env; + char *h; + char *bstart; + STRING s; + + h = so_text(outs); + + /* look for start of body */ + bstart = strstr(h,"\r\n\r\n"); + + if (!bstart) { + q_status_message(SM_ORDER,1,1,"Encrypted data couldn't be parsed."); + } else { + bstart += 4; /* skip over CRLF*2 */ + + INIT(&s,mail_string,bstart,strlen(bstart)); + rfc822_parse_msg_full(&env,&body,h,bstart-h-2,&s,BADHOST,0,0); + mail_free_envelope(&env); /* Don't care about this */ + + /* + Now convert original body (application/pkcs7-mime) + to a multipart body with one sub-part (the decrypted body) + Note that the sub-part may also be multipart! + */ + + b->type = TYPEMULTIPART; + if (b->subtype) fs_give((void**) &b->subtype); + + /* This subtype is used in mailview.c to annotate the display of + encrypted or signed messages. We know for sure then that it's a PKCS7 + part because the sparep field is set to the PKCS7 object (see above) + */ + b->subtype = cpystr("x-pkcs7-enclosure"); + b->encoding = ENC8BIT; + + if (b->description) fs_give ((void**) &b->description); + b->description = cpystr(what_we_did); + + if (b->disposition.type) fs_give ((void **) &b->disposition.type); + + if (b->contents.text.data) fs_give ((void **) &b->contents.text.data); + + if (b->parameter) mail_free_body_parameter(&b->parameter); + + /* Allocate mem for the sub-part, and copy over the contents of our parsed body */ + b->nested.part = fs_get(sizeof (PART)); + b->nested.part->body = *body; + b->nested.part->next = NULL; + + fs_give((void**) &body); + + /* IMPORTANT BIT: set the body->contents.text.data elements to contain the decrypted + data. Otherwise, it'll try to load it from the original data. Eek. + */ + create_local_cache(bstart,&b->nested.part->body); + + result = 1; + } + } + + end: + + BIO_free(out); + + if (outs) + so_give(&outs); + + return result; + } + + /* + Recursively handle PKCS7 bodies in our message. + + Returns non-zero if some fiddling was done. + */ + static int do_fiddle_smime_message(BODY *b,int msgno,char *section) + { + int result = 0; + + if (is_pkcs7_body(b)) { + + if (do_decoding(b,msgno,*section ? section : "1")) { + /* + b should now be a multipart message: fiddle it too in case it's been multiply-encrypted! + */ + + /* fallthru */ + result = 1; + } + } + + if (b->type==TYPEMULTIPART) { + + PART *p; + int partNum; + char newSec[100]; + + if (b->subtype && strucmp(b->subtype,"signed")==0) { + + /* Ahah. We have a multipart signed entity. */ + + /* part 1 (signed thing) + part 2 (the pkcs7 object) + */ + + do_detached_signature_verify(b,msgno,section); + + } else { + + for (p=b->nested.part,partNum=1;p;p=p->next,partNum++) { + + /* Append part number to the section string */ + + snprintf(newSec,sizeof(newSec),"%s%s%d",section,*section ? "." : "",partNum); + + result |= do_fiddle_smime_message(&p->body,msgno,newSec); + } + + } + + } + + return result; + } + + /* + Fiddle a message in-place by decrypting/verifying S/MIME entities. + Returns non-zero if something was changed. + */ + + int fiddle_smime_message(BODY *b,int msgno,int is_new_message) + { + if (F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global)) + forget_private_keys(); + return do_fiddle_smime_message(b,msgno,""); + } + + + /********************************************************************************/ + + static struct key smime_info_keys[] = + {HELP_MENU, + OTHER_MENU, + {"<","Back",{MC_VIEW_TEXT,2,{'<',','}},KS_EXITMODE}, + NULL_MENU, + NULL_MENU, + NULL_MENU, + PREVPAGE_MENU, + NEXTPAGE_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + + HELP_MENU, + OTHER_MENU, + MAIN_MENU, + QUIT_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + INDEX_MENU, + NULL_MENU, + NULL_MENU, + }; + INST_KEY_MENU(smime_info_keymenu, smime_info_keys); + + #define SMIME_PARENT_KEY 2 + + static void get_fingerprint(X509 *cert,const EVP_MD *type,char *buf,int maxLen) + { + unsigned char md[128]; + char *b; + int len,i; + + len = sizeof(md); + + X509_digest(cert,type,md,&len); + + b = buf; + *b = 0; + for (i=0; i=maxLen) + break; + + if (i != 0) + *b++ = ':'; + sprintf(b,"%02x",md[i]); + b+=2; + } + } + + static void output_X509_NAME(X509_NAME *name,gf_io_t pc) + { + int i,c; + int nid; + char buf[256]; + + c = X509_NAME_entry_count(name); + + for (i=c-1;i>=0;i--) { + X509_NAME_ENTRY *e; + + e = X509_NAME_get_entry(name,i); + if (!e) continue; + + X509_NAME_get_text_by_OBJ(name, e->object,buf,sizeof(buf)); + + gf_puts(buf,pc); + gf_puts(NEWLINE,pc); + } + + } + + /* + Output a string in a distinctive style + */ + static void gf_puts_uline(const char *txt,gf_io_t pc) + { + #if 0 + pc(TAG_EMBED); pc(TAG_ULINEON); + gf_puts(txt,pc); + pc(TAG_EMBED); pc(TAG_ULINEOFF); + #else + pc(TAG_EMBED); pc(TAG_BOLDON); + gf_puts(txt,pc); + pc(TAG_EMBED); pc(TAG_BOLDOFF); + #endif + } + + /* + Get a line from the given store (including \n) + */ + static int so_gets(STORE_S *store,char *buf,int len) + { + unsigned char c; + char *bend = buf + len - 1; + char *b = buf; + + do { + if (!store->readc(&c,store)) { + *b = 0; + return b!=buf; + } + *b++ = c; + } while (c!='\n' && bttyo->screen_cols/2 - 1; + + so_seek(left,0,0); + so_seek(right,0,0); + + left_wrapped = wrap_store(left,w); + right_wrapped = wrap_store(right,w); + + so_seek(left_wrapped,0,0); + so_seek(right_wrapped,0,0); + + for (;;) { + + i = so_gets(left_wrapped,buf_l,sizeof(buf_l)); + i += so_gets(right_wrapped,buf_r,sizeof(buf_r)); + + if (i==0) + break; + + for (i=0, b=buf_l;ittyo->screen_cols * percent / 100; + start = (ps_global->ttyo->screen_cols - len)/2; + + for (i=0;icert_info) { + gf_puts("Couldn't find certificate info.",spc); + gf_puts(NEWLINE,spc); + } else { + + gf_puts_uline("Subject (whose certificate it is)",spc); + gf_puts(NEWLINE, spc); + + output_X509_NAME(cert->cert_info->subject,spc); + gf_puts(NEWLINE,spc); + + gf_puts_uline("Serial Number",spc); + gf_puts(NEWLINE,spc); + + sprintf(buf,"%d",ASN1_INTEGER_get(cert->cert_info->serialNumber)); + gf_puts(buf,spc); + gf_puts(NEWLINE,spc); + gf_puts(NEWLINE,spc); + + gf_puts_uline("Validity",spc); + gf_puts(NEWLINE,spc); + { + BIO *mb = BIO_new_so(left); + + gf_puts("Not Before: ",spc); + ASN1_UTCTIME_print(mb,cert->cert_info->validity->notBefore); + BIO_flush(mb); + gf_puts(NEWLINE,spc); + + gf_puts("Not After: ",spc); + ASN1_UTCTIME_print(mb,cert->cert_info->validity->notAfter); + BIO_flush(mb); + + gf_puts(NEWLINE,spc); + gf_puts(NEWLINE,spc); + + BIO_free(mb); + } + + } + + gf_clear_so_writec(left); + + gf_set_so_writec(&spc,right); + + if (!cert->cert_info) { + gf_puts("Couldn't find certificate info.",spc); + gf_puts(NEWLINE,spc); + } else { + gf_puts_uline("Issuer",spc); + gf_puts(NEWLINE, spc); + + output_X509_NAME(cert->cert_info->issuer,spc); + gf_puts(NEWLINE,spc); + } + + gf_clear_so_writec(right); + + side_by_side(left,right,pc); + + gf_puts_uline("SHA1 Fingerprint",pc); + gf_puts(NEWLINE,pc); + get_fingerprint(cert,EVP_sha1(),buf,sizeof(buf)); + gf_puts(buf,pc); + gf_puts(NEWLINE,pc); + + gf_puts_uline("MD5 Fingerprint",pc); + gf_puts(NEWLINE,pc); + get_fingerprint(cert,EVP_md5(),buf,sizeof(buf)); + gf_puts(buf,pc); + gf_puts(NEWLINE,pc); + + so_give(&left); + so_give(&right); + } + + void format_smime_info(int pass,BODY *body,int msgno,gf_io_t pc) + { + PKCS7 *p7; + int i; + + if (body->type==TYPEMULTIPART) { + PART *p; + + for (p=body->nested.part;p;p=p->next) { + format_smime_info(pass,&p->body,msgno,pc); + } + } + + p7 = body->sparep; + if (p7) { + + if (PKCS7_type_is_signed(p7)) { + STACK_OF(X509) *signers; + + switch (pass) { + case 1: + gf_puts("This message was cryptographically signed." NEWLINE,pc); + break; + case 2: + + signers = PKCS7_get0_signers(p7, NULL, 0); + + if (signers) { + + sprintf(tmp_20k_buf,"Certificate%s used for signing",plural(sk_X509_num(signers))); + gf_puts_uline(tmp_20k_buf,pc); + gf_puts(NEWLINE,pc); + print_separator_line(100,'-',pc); + + for (i=0;id.enveloped && p7->d.enveloped->enc_data) { + X509_ALGOR *alg = p7->d.enveloped->enc_data->algorithm; + STACK_OF(PKCS7_RECIP_INFO) *ris = p7->d.enveloped->recipientinfo; + int found = 0; + + gf_puts("The algorithm used to encrypt was ",pc); + + + if (alg) { + char *n = OBJ_nid2sn( OBJ_obj2nid(alg->algorithm )); + + gf_puts(n ? n : "",pc); + + } else gf_puts("",pc); + + gf_puts("." NEWLINE NEWLINE,pc); + + sprintf(tmp_20k_buf,"Certificate%s for decrypting",plural(sk_PKCS7_RECIP_INFO_num(ris))); + gf_puts_uline(tmp_20k_buf,pc); + gf_puts(NEWLINE,pc); + print_separator_line(100,'-',pc); + + for (i=0;icert,pc); + gf_puts(NEWLINE,pc); + + } + } + + if (!found) { + gf_puts("No certificate capable of decrypting could not be found.",pc); + } + } + break; + } + + } + } + } + + void view_writec(); + + void smime_info_screen(struct pine *ps) + { + int msgno; + OtherMenu what; + int cmd; + char backtag[64]; + BODY *body; + ENVELOPE *env; + HANDLE_S *handles = NULL; + SCROLL_S scrollargs; + STORE_S *store = NULL; + int offset = 0; + + ps->prev_screen = smime_info_screen; + ps->next_screen = SCREEN_FUN_NULL; + + if(mn_total_cur(ps->msgmap) > 1L){ + q_status_message(SM_ORDER | SM_DING, 0, 3, + "Can only view one message's information at a time."); + return; + } + /* else check for existence of smime bits */ + + msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)); + + env = mail_fetch_structure (ps->mail_stream,msgno, + &body,0); + if (!env || !body) { + q_status_message(SM_ORDER, 0, 3, + "Can't fetch body of message."); + return; + } + + what = FirstMenu; + + store = so_get(CharStar, NULL, EDIT_ACCESS); + + while(ps->next_screen == SCREEN_FUN_NULL){ + + ClearLine(1); + + so_truncate(store,0); + + view_writec_init(store, &handles, HEADER_ROWS(ps), + HEADER_ROWS(ps) + + ps->ttyo->screen_rows - (HEADER_ROWS(ps) + + HEADER_ROWS(ps))); + + gf_puts_uline("Overview",view_writec); + gf_puts(NEWLINE,view_writec); + + format_smime_info(1,body,msgno,view_writec); + gf_puts(NEWLINE,view_writec); + format_smime_info(2,body,msgno,view_writec); + + view_writec_destroy(); + + + ps->next_screen = SCREEN_FUN_NULL; + + memset(&scrollargs, 0, sizeof(SCROLL_S)); + scrollargs.text.text = so_text(store); + scrollargs.text.src = CharStar; + scrollargs.text.desc = "S/MIME information"; + scrollargs.body_valid = 1; + + if(offset){ /* resize? preserve paging! */ + scrollargs.start.on = Offset; + scrollargs.start.loc.offset = offset; + offset = 0L; + } + + scrollargs.bar.title = "S/MIME INFORMATION"; + /* scrollargs.end_scroll = view_end_scroll; */ + scrollargs.resize_exit = 1; + scrollargs.help.text = NULL; + scrollargs.help.title = "HELP FOR S/MIME INFORMATION VIEW"; + scrollargs.keys.menu = &smime_info_keymenu; + scrollargs.keys.what = what; + setbitmap(scrollargs.keys.bitmap); + + if(scrolltool(&scrollargs) == MC_RESIZE) + offset = scrollargs.start.loc.offset; + } + + so_give(&store); + + } + + + #endif /* SMIME */ diff -Ncr pine4.53/pine/smime.h pine4.53-smime/pine/smime.h *** pine4.53/pine/smime.h Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/smime.h Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,17 ---- + #ifdef SMIME + + int is_pkcs7_body(BODY *b); + + int fiddle_smime_message(BODY *b,int msgno,int is_new_message); + + int encrypt_outgoing_message(METAENV *header,BODY **bodyP); + int sign_outgoing_message(METAENV *header,BODY **bodyP,int dont_detach); + int get_passphrase(void); + void smime_info_screen(struct pine *ps); + + extern int g_need_passphrase; + + extern int g_do_encrypt; + extern int g_do_sign; + + #endif /* SMIME */ diff -Ncr pine4.53/pine/smkeys.c pine4.53-smime/pine/smkeys.c *** pine4.53/pine/smkeys.c Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/smkeys.c Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,343 ---- + #ifdef SMIME + + #include "headers.h" + + #include + #include + #include + #include + #include + #include + + #include "smkeys.h" + + /*--------------------------------------------------- + Remove leading whitespace, trailing whitespace and convert + to lowercase. Also remove slash characters + + Args: s, -- The string to clean + + Result: the cleaned string + ----*/ + static char * + emailstrclean(string) + char *string; + { + char *s = string, *sc = NULL, *p = NULL; + + for(; *s; s++){ /* single pass */ + if(!isspace((unsigned char)*s)){ + p = NULL; /* not start of blanks */ + if(!sc) /* first non-blank? */ + sc = string; /* start copying */ + } + else if(!p) /* it's OK if sc == NULL */ + p = sc; /* start of blanks? */ + + if(sc && *s!='/' && *s!='\\') /* if copying, copy */ + *sc++ = isupper((unsigned char)(*s)) + ? (unsigned char)tolower((unsigned char)(*s)) + : (unsigned char)(*s); + } + + if(p) /* if ending blanks */ + *p = '\0'; /* tie off beginning */ + else if(!sc) /* never saw a non-blank */ + *string = '\0'; /* so tie whole thing off */ + + return(string); + } + + /* + Add a lookup for each "*.crt" file in the given directory. + */ + static void add_certs_in_dir(X509_LOOKUP *lookup,const char *path) + { + char buf[MAXPATH]; + struct direct *d; + DIR *dirp; + PERSONAL_CERT *result; + + result = NULL; + + dirp = opendir(path); + if (dirp) { + + while (d=readdir(dirp)) { + BIO *in; + X509 *cert; + + if (srchrstr(d->d_name,".crt")) { + + build_path(buf,(char*) path,d->d_name,sizeof(buf)); + + X509_LOOKUP_load_file(lookup,buf,X509_FILETYPE_PEM); + } + + } + + closedir(dirp); + } + } + + /* Get an X509_STORE. This consists of the system + certs directory and any certificates in the user's + ~/.pine-smime/ca directory. + */ + X509_STORE *get_ca_store(const char *path) + { + X509_LOOKUP *lookup; + char buf[MAXPATH]; + + X509_STORE *store; + + store=X509_STORE_new(); + + lookup=X509_STORE_add_lookup(store,X509_LOOKUP_file()); + + build_path(buf,(char*) path,"ca",sizeof(buf)); + + add_certs_in_dir(lookup,buf); + + /* X509_LOOKUP_load_file(lookup,NULL,X509_FILETYPE_DEFAULT); */ + + lookup = X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir()); + + X509_LOOKUP_add_dir(lookup,SSL_CERT_DIRECTORY,X509_FILETYPE_PEM); + + /* X509_STORE_set_default_paths(cert_store); + X509_STORE_load_locations(cert_store,NULL,"../../certs"); + X509_STORE_set_verify_cb_func(cert_store,verify_callback); + */ + + return store; + } + + static EVP_PKEY *load_key(char *file, char *pass) + { + BIO *in; + EVP_PKEY *key; + if(!(in = BIO_new_file(file, "r"))) return NULL; + key = PEM_read_bio_PrivateKey(in, NULL,NULL,pass); + BIO_free(in); + return key; + } + + char *get_x509_name_entry(const char *key,X509_NAME *name) + { + int i,c,n; + char buf[256]; + char *id; + + if (!name) + return NULL; + + c = X509_NAME_entry_count(name); + + for (i=0;iobject); + if ((n == NID_undef) || ((id=(char*) OBJ_nid2sn(n)) == NULL)) { + i2t_ASN1_OBJECT(buf,sizeof(buf),e->object); + id = buf; + } + + if ((strucmp(id,"email")==0) || (strucmp(id,"emailAddress")==0)) { + X509_NAME_get_text_by_OBJ(name, e->object,(char*) buf,sizeof(buf)-1); + return cpystr(buf); + } + } + return NULL; + } + + char *get_x509_subject_email(X509 *x) + { + char* result; + result = get_x509_name_entry("email",X509_get_subject_name(x)); + if ( !result ) { + result = get_x509_name_entry("emailAddress",X509_get_subject_name(x)); + } + + return result; + } + + /* Save the certificate for the given email address in + ~/.pine-smime/public. + + Should consider the security hazards in making a file with + the email address that has come from the certificate. + + The argument email is destroyed. + */ + + void save_cert_for(const char *path,char *email,X509 *cert) + { + char sf[MAXPATH]; + char sf2[MAXPATH]; + BIO *tmp; + + build_path(sf,(char*) path,"public",sizeof(sf)); + + build_path(sf2,sf,emailstrclean(email),sizeof(sf2)); + strncat(sf2,".crt",sizeof(sf2)-1-strlen(sf2)); + sf2[sizeof(sf2)-1] = 0; + + tmp = BIO_new_file(sf2, "w"); + if (tmp) { + X509_print(tmp,cert); + PEM_write_bio_X509_AUX(tmp, cert); + BIO_free(tmp); + q_status_message1(SM_ORDER,1,1,"Saved certificate for <%s>",(char*)email); + } else { + q_status_message1(SM_ORDER,1,1,"Couldn't save certificate for <%s>",(char*)email); + } + } + + /* + Try to retrieve the certificate for the given email address. + */ + X509 *get_cert_for(const char *path,const char *email) + { + char buf[MAXPATH]; + char buf2[MAXPATH]; + char buf3[MAXPATH]; + char *p; + X509 *cert = NULL; + BIO *in; + + strncpy(buf3,email,sizeof(buf3)-1); + buf3[sizeof(buf3)-1] = 0; + + /* clean it up (lowercase, space removal) */ + emailstrclean(buf3); + + build_path(buf,(char*)path,"public",sizeof(buf)); + build_path(buf2,buf,buf3,sizeof(buf2)); + strncat(buf2,".crt",sizeof(buf2)-1-strlen(buf2)); + buf2[sizeof(buf2)-1] = 0; + + if((in = BIO_new_file(buf2, "r"))!=0) { + + cert = PEM_read_bio_X509(in, NULL, NULL,NULL); + + if (cert) { + /* could check email addr in cert matches */ + } + } + BIO_free(in); + + return cert; + } + + EVP_PKEY *get_key_for(const char *path,const char *email,const char *pass) + { + char buf[MAXPATH]; + char buf2[MAXPATH]; + char buf3[MAXPATH]; + char *p; + EVP_PKEY *key = NULL; + BIO *in; + + strncpy(buf3,email,sizeof(buf3)-1); + buf3[sizeof(buf3)-1] = 0; + + /* clean it up (lowercase, space removal) */ + emailstrclean(buf3); + + build_path(buf,(char*)path,"private",sizeof(buf)); + build_path(buf2,buf,buf3,sizeof(buf2)); + strncat(buf2,".key",sizeof(buf2)-1-strlen(buf2)); + buf2[sizeof(buf2)-1] = 0; + + key = load_key(buf2,pass); + + return key; + } + + /* + Load the user's personal certificates from + ~/.pine-smime/private + */ + PERSONAL_CERT *get_personal_certs(const char *path) + { + char buf[MAXPATH]; + char buf2[MAXPATH]; + struct direct *d; + DIR *dirp; + PERSONAL_CERT *result; + + result = NULL; + + build_path(buf,(char*) path,"private",sizeof(buf)); + + dirp = opendir(buf); + if (dirp) { + + while (d=readdir(dirp)) { + BIO *in; + X509 *cert; + + if (srchrstr(d->d_name,".key")) { + + /* copy file name to temp buffer */ + strcpy(buf2,d->d_name); + /* chop off ".key" trailier */ + buf2[strlen(buf2)-4] = 0; + /* Look for certificate */ + cert = get_cert_for(path,buf2); + + if (cert) { + PERSONAL_CERT *pc; + + /* create a new PERSONAL_CERT, fill it in */ + + pc = fs_get(sizeof(PERSONAL_CERT)); + pc->cert = cert; + build_path(buf2,buf,d->d_name,sizeof(buf2)); + pc->file = cpystr(buf2); + + strcpy(pc->file + strlen(pc->file) - 4, ".key"); + + /* Try to load the key with an empty password */ + pc->key = load_key(pc->file,""); + + pc->next = result; + result = pc; + } + } + + } + + closedir(dirp); + } + + return result; + } + + void personal_cert_free(PERSONAL_CERT **pcp) + { + if (pcp && *pcp) { + + PERSONAL_CERT *pc = *pcp; + + fs_give((void**) &pc->file); + + X509_free(pc->cert); + + if (pc->key) + EVP_PKEY_free(pc->key); + + personal_cert_free(&pc->next); + + fs_give((void**) pcp); + } + } + + #endif /* SMIME */ diff -Ncr pine4.53/pine/smkeys.h pine4.53-smime/pine/smkeys.h *** pine4.53/pine/smkeys.h Wed Dec 31 16:00:00 1969 --- pine4.53-smime/pine/smkeys.h Wed Jan 15 12:48:15 2003 *************** *** 0 **** --- 1,20 ---- + + #define PERSONAL_CERT struct personal_cert + + PERSONAL_CERT { + X509 *cert; + EVP_PKEY *key; + char *file; + PERSONAL_CERT *next; + }; + + X509_STORE *get_ca_store(const char *d); + + PERSONAL_CERT *get_personal_certs(const char *d); + + X509 *get_cert_for(const char *path,const char *email); + void save_cert_for(const char *path,char *email,X509 *cert); + + void personal_cert_free(PERSONAL_CERT **pc); + + char *get_x509_subject_email(X509 *x);