diff --git a/doc/DETAILS b/doc/DETAILS index ba66248ec..11a540af6 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -241,6 +241,10 @@ more arguments in future versions. POLICY_URL string is %XX escaped + BEGIN_STREAM + END_STREAM + Issued by pipemode. + Key generation ============== @@ -669,6 +673,41 @@ Usage of gdbm files for keyrings +Pipemode +======== +This mode can be used to perform multiple operations with one call to +gpg. It comes handy in cases where you have to verify a lot of +signatures. Currently we support only detached signatures. This mode +is a kludge to avoid running gpg n daemon mode and using Unix Domain +Sockets to pass the data to it. There is no easy portable way to do +this under Windows, so we use plain old pipes which do work well under +Windows. Because there is no way to signal multiple EOFs in a pipe we +have to embed control commands in the data stream: We distinguish +between a data state and a control state. Initially the system is in +data state but it won't accept any data. Instead it waits for +transition to control state which is done by sending a single '@' +character. While in control state the control command os expected and +this command is just a single byte after which the system falls back +to data state (but does not necesary accept data now). The simplest +control command is a '@' which just inserts this character into the +data stream. + +Here is the format we use for detached signatures: +"@<" - Begin of new stream +"@B" - Detached signature follows. + This emits a control packet (1,'B') +detached_signature +"@t" - Signed text follows. + This emits the control packet (2, 'B') +signed_text +"@." - End of operation. The final control packet forces signature + verification +"@>" - End of stream + + + + + Other Notes =========== diff --git a/g10/ChangeLog b/g10/ChangeLog index 9ebb3378c..b4cf1cd7f 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,11 @@ +2000-12-08 Werner Koch + + * pipemode.c: Made the command work. Currently only for + non-armored detached signatures. + * mainproc.c (release_list): Reset the new pipemode vars. + (add_gpg_control): Handle the control packets for pipemode + * status.c, status.h: New stati {BEGIN,END}_STREAM. + 2000-12-07 Werner Koch * g10.c: New option --allow-secret-key-import. diff --git a/g10/mainproc.c b/g10/mainproc.c index e9ac0ddc6..3b1670094 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -72,6 +72,10 @@ struct mainproc_context { ulong local_id; /* ditto */ struct kidlist_item *failed_pkenc; /* list of packets for which we do not have a secret key */ + struct { + int op; + int stop_now; + } pipemode; }; @@ -97,6 +101,8 @@ release_list( CTX c ) c->list = NULL; c->have_data = 0; c->last_was_session_key = 0; + c->pipemode.op = 0; + c->pipemode.stop_now = 0; m_free(c->dek); c->dek = NULL; } @@ -136,6 +142,31 @@ add_gpg_control( CTX c, PACKET *pkt ) * Process the last one and reset everything */ release_list(c); } + else if ( pkt->pkt.gpg_control->control == 2 ) { + /* Pipemode control packet */ +#warning We have to do some sanit checks all over the place + if ( pkt->pkt.gpg_control->datalen < 2 ) + log_fatal ("invalid pipemode control packet length\n"); + if (pkt->pkt.gpg_control->data[0] == 1) { + /* start the whole thing */ + assert ( !c->list ); /* we should be in a pretty virgin state */ + assert ( !c->pipemode.op ); + c->pipemode.op = pkt->pkt.gpg_control->data[1]; + } + else if (pkt->pkt.gpg_control->data[0] == 2) { + /* the signed material follows in a plaintext packet */ + assert ( c->pipemode.op == 'B' ); + } + else if (pkt->pkt.gpg_control->data[0] == 3) { + assert ( c->pipemode.op == 'B' ); + release_list (c); + /* and tell the outer loop to terminate */ + c->pipemode.stop_now = 1; + } + else + log_fatal ("invalid pipemode control packet code\n"); + return 0; /* no need to store the packet */ + } if( c->list ) /* add another packet */ add_kbnode( c->list, new_kbnode( pkt )); @@ -1094,6 +1125,12 @@ do_proc_packets( CTX c, IOBUF a ) } else free_packet(pkt); + if ( c->pipemode.stop_now ) { + /* we won't get an EOF in pipemode, so we have to + * break the loop here */ + rc = -1; + break; + } } if( rc == G10ERR_INVALID_PACKET ) write_status_text( STATUS_NODATA, "3" ); diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 30566a8cb..dbaca3822 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1796,6 +1796,7 @@ parse_mdc( IOBUF inp, int pkttype, unsigned long pktlen, * The format of such a control packet is: * n byte session marker * 1 byte control type: 1 = Clearsign hash info + * 2 = Pipemode control * m byte control data */ diff --git a/g10/pipemode.c b/g10/pipemode.c index 4d9f0a31a..986ea8596 100644 --- a/g10/pipemode.c +++ b/g10/pipemode.c @@ -38,12 +38,15 @@ #define CONTROL_PACKET_SPACE 30 +#define FAKED_LITERAL_PACKET_SPACE (9+2+2) enum pipemode_state_e { STX_init = 0, STX_wait_operation, STX_begin, STX_text, + STX_detached_signature, + STX_signed_data, STX_wait_init }; @@ -52,6 +55,7 @@ struct pipemode_context_s { enum pipemode_state_e state; int operation; int stop; + int block_mode; }; @@ -89,12 +93,23 @@ pipemode_filter( void *opaque, int control, if( control == IOBUFCTRL_UNDERFLOW ) { *ret_len = 0; /* reserve some space for one control packet */ - if ( size <= CONTROL_PACKET_SPACE ) + if ( size <= CONTROL_PACKET_SPACE+FAKED_LITERAL_PACKET_SPACE ) BUG(); - size -= CONTROL_PACKET_SPACE; + size -= CONTROL_PACKET_SPACE+FAKED_LITERAL_PACKET_SPACE; + if ( stx->block_mode ) { + /* reserve 2 bytes for the block length */ + buf[n++] = 0; + buf[n++] = 0; + } + while ( n < size ) { + /* FIXME: we have to make sure that we have a large enough + * buffer for a control packet even after we already read + * something. The easest way to do this is probably by ungetting + * the control sequenceand and returning the buffer we have + * already assembled */ int c = iobuf_get (a); if (c == -1) { if ( stx->state != STX_init ) { @@ -120,6 +135,7 @@ pipemode_filter( void *opaque, int control, return -1; } stx->state = STX_wait_operation; + stx->block_mode = 0; break; case '>': /* end of stream part */ if ( stx->state != STX_wait_init ) { @@ -141,35 +157,64 @@ pipemode_filter( void *opaque, int control, return -1; } stx->operation = c; - stx->state = STX_begin; - n += make_control ( buf, 1, stx->operation ); + stx->state = c == 'B'? STX_detached_signature + : STX_begin; + n += make_control ( buf+n, 1, stx->operation ); + /* must leave after a control packet */ goto leave; case 't': /* plaintext text follows */ - if ( stx->state != STX_begin ) { + if ( stx->state == STX_detached_signature ) { + if ( stx->operation != 'B' ) { + log_error ("invalid operation for this state\n"); + stx->stop = 1; + return -1; + } + stx->state = STX_signed_data; + n += make_control ( buf+n, 2, 'B' ); + /* and now we fake a literal data packet much the same + * as in armor.c */ + buf[n++] = 0xaf; /* old packet format, type 11, + var length */ + buf[n++] = 0; /* set the length header */ + buf[n++] = 6; + buf[n++] = 'b'; /* we ignore it anyway */ + buf[n++] = 0; /* namelength */ + memset(buf+n, 0, 4); /* timestamp */ + n += 4; + /* and return now so that we are sure to have + * more space in the bufer for the next control + * packet */ + stx->block_mode = 1; + goto leave2; + } + else { log_error ("invalid state for @t\n"); stx->stop = 1; return -1; } - if ( stx->operation != 'E' ) { - log_error ("invalid operation for @t\n"); - stx->stop = 1; - return -1; - } - stx->state = STX_text; - n += make_control ( buf, 2, c ); - goto leave; + break; case '.': /* ready */ - if ( stx->state == STX_text ) - ; + if ( stx->state == STX_signed_data ) { + if (stx->block_mode) { + buf[0] = (n-2) >> 8; + buf[1] = (n-2); + if ( buf[0] || buf[1] ) { + /* end of blocks marker */ + buf[n++] = 0; + buf[n++] = 0; + } + stx->block_mode = 0; + } + n += make_control ( buf+n, 3, 'B' ); + } else { log_error ("invalid state for @.\n"); stx->stop = 1; return -1; } stx->state = STX_wait_init; - n += make_control ( buf, 3, c ); goto leave; default: @@ -191,6 +236,13 @@ pipemode_filter( void *opaque, int control, stx->stop = 1; rc = -1; /* eof */ } + if ( stx->block_mode ) { + /* fixup the block length */ + buf[0] = (n-2) >> 8; + buf[1] = (n-2); + } + leave2: + log_hexdump ("pipemode:", buf, n ); *ret_len = n; } else if( control == IOBUFCTRL_DESC ) @@ -211,16 +263,19 @@ run_in_pipemode(void) memset( &afx, 0, sizeof afx); memset( &stx, 0, sizeof stx); + /* FIXME: We have to handle de-armoring somehow. We can't rely on + * the standard armor filter becuase it checks only once whether armoring + * is required and it would try to unarmor everything which is not good. + * So, currently only non-armored detached signatures do work. + */ + fp = iobuf_open("-"); iobuf_push_filter (fp, pipemode_filter, &stx ); - if( !opt.no_armor ) - iobuf_push_filter( fp, armor_filter, &afx ); - do { - log_debug ("pipemode: begin proc_packets\n"); + write_status (STATUS_BEGIN_STREAM); rc = proc_packets( NULL, fp ); - log_debug ("pipemode: end proc_packets: %s\n", g10_errstr (rc)); + write_status (STATUS_END_STREAM); } while ( !stx.stop ); } @@ -229,3 +284,4 @@ run_in_pipemode(void) + diff --git a/g10/status.c b/g10/status.c index 98575b632..8979be979 100644 --- a/g10/status.c +++ b/g10/status.c @@ -134,6 +134,8 @@ get_status_string ( int no ) case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break; case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break; case STATUS_POLICY_URL : s = "POLICY_URL" ; break; + case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break; + case STATUS_END_STREAM : s = "END_STREAM"; break; default: s = "?"; break; } return s; diff --git a/g10/status.h b/g10/status.h index 2c946ad65..e3cd95a5a 100644 --- a/g10/status.h +++ b/g10/status.h @@ -85,6 +85,9 @@ #define STATUS_NOTATION_NAME 53 #define STATUS_NOTATION_DATA 54 #define STATUS_POLICY_URL 55 +#define STATUS_BEGIN_STREAM 56 +#define STATUS_END_STREAM 57 + /*-- status.c --*/ void set_status_fd ( int fd );