/* * Support routines for hpss script interface with the OSU web server * HPSS MST. This code is not thread safe. * * All functions return a VMS-style condition code as their return value. * * Author: David Jones * Date: 7-FEB-2000 */ #include #include #include #include /* case conversion */ #include "hpss_share.h" /* validate prototypes. */ #include "../base_code/hpss_msg.h" #include /* VMS mailbox flags */ #include /* VMS QIO function codes */ #include /* VMS logical name services */ #include /* VMS system service status codes */ #include /* VMS job/process info */ #include /* VMS Object set security service */ #include /* VMS access control list entries */ int SYS$CREMBX(), SYS$QIOW(), SYS$CRELNM(), SYS$DELLNM(), SYS$DELMBX(); int SYS$ASSIGN(), SYS$DASSGN(), SYS$CANCEL(), SYS$SETIMR(); int SYS$CANTIM(), SYS$SETAST(), SYS$SYNCH(), SYS$QIO(), LIB$EMUL(); int SYS$WAKE(), SYS$HIBER(), SYS$ASCTOID(), SYS$SET_SECURITY(); static $DESCRIPTOR(mbxlogical_table,"LNM$PROCESS_DIRECTORY"); static $DESCRIPTOR(mbxlogical_destination,"LNM$TEMPORARY_MAILBOX"); #define TEMP_MAILBOX_TABLE "LNM$GROUP" /* * Define structures to manage the asynchronous output stream. In general * the aio_stream structures must be accessed from AST level or with ASTs * disabled. */ struct aio_buffer { struct aio_buffer *next; int length; /* number of bytes to write */ char buffer[HPSS_MAX_OUT_MSGSIZE]; }; struct aio_stream { struct aio_stream *next; int total_count; /* bytes transferred */ struct { unsigned short status, count; long pid; } iosb; int channel; /* channel assigned to mailbox */ int unit; /* mailbox unit number */ int abort; /* Set if last write failed */ int detached; /* If set rundown when no pending i/o */ struct aio_buffer *first; /* Null if no pending */ struct aio_buffer *last; }; static int stream_limit = 0; /* set to 1/2 AST limit */ static int stream_count; static int stream_alloc_pending = 0; static struct aio_stream *free_stream; static struct aio_buffer *free_buffer; /* * An hpss_context structure holds the persistent state for a context * number between calls. */ struct sym_table_def { int nlen; int vlen; char *name; char *value; }; struct sym_table { int allocation; /* size allocated, number of entries */ int size; /* currently used */ struct sym_table_def *def; /* dynamically allocated */ }; struct hpss_context { struct hpss_context *next; int id; /* context number given to application */ int channel; /* VMS channel to input mailbox */ int outchan; /* output channel */ int msglen; /* length of current message */ int accept_timer_active; int accept_timer_fired; union hpss_mbxmsg msg; struct aio_stream *out_stream; struct aio_buffer *outbuf; struct sym_table var; /* expands cgi var table in msg buffer */ struct sym_table form; /* form data */ int content_alloc; int content_used; int content_pos; char *content; }; static struct hpss_context *context_list = (struct hpss_context *) 0; /***************************************************************************/ /* The following set of routines manage the asynchronous writes to the * output mailboxes. If the application issues writes to the output mailbox * faster than the web server reads them, the buffers are queued and * resent at AST layer. */ static void aio_stream_ast ( struct aio_stream *stream ) { int status; struct aio_buffer *cur; /* * Dequeue the I/O buffer and check completion status of the I/O. */ cur = stream->first; if ( !cur ) { fprintf(stderr,"BUGCHECK, aio_stream_ast sees null current buffer\n"); return; } stream->first = cur->next; status = stream->iosb.status; if ( (status&1) == 0 ) { /* * Error occured, kill the stream, cancelling pending I/O's. */ stream->last->next = free_buffer; free_buffer = cur; stream->first = (struct aio_buffer *) 0; stream->abort = 1; return; } else { /* * Succesful completion, keep track of total bytes transferred. */ stream->total_count += stream->iosb.count; cur->next = free_buffer; free_buffer = cur; } /* * Start new I/O if another buffer present. */ if ( stream->first ) { status = SYS$QIO ( 0, stream->channel, IO$_WRITEVBLK|IO$M_READERCHECK, &stream->iosb, aio_stream_ast, stream, stream->first->buffer, stream->first->length, 0, 0, 0, 0 ); if ( (status&1) == 0 ) { stream->last->next = free_buffer; free_buffer = stream->first; stream->first = (struct aio_buffer *) 0; stream->abort = 1; } } else if ( stream->detached ) { /* * Main thread no longer referencing this stream, run it down. */ SYS$DASSGN ( stream->channel ); stream->next = free_stream; free_stream = stream; stream_count--; if ( stream_alloc_pending ) SYS$WAKE ( 0, 0 ); } } static struct aio_buffer *allocate_aio_buffer ( ) { struct aio_buffer *buf; SYS$SETAST(0); buf = free_buffer; if ( buf ) free_buffer = buf->next; SYS$SETAST(1); if ( !buf ) { int i; buf = (struct aio_buffer *) malloc ( sizeof(struct aio_buffer)*4 ); if ( !buf ) return buf; for ( i = 1; i < 3; i++ ) buf[i].next = &buf[i+1]; SYS$SETAST(0); buf[3].next = free_buffer; free_buffer = &buf[1]; SYS$SETAST(1); } return buf; } static int write_to_aio_stream ( struct aio_buffer *buffer, struct aio_stream *stream, struct aio_buffer **next_buffer ) { int status; struct aio_buffer *pending_buf, *next_buf; /* * Queue buffer to AST level and pre-allocate next buffer while * we have ASTs disabled. */ SYS$SETAST(0); if ( stream->abort ) { /* * I/O to stream has been aborted by AST layer. */ status = stream->iosb.status; SYS$SETAST(1); if ( ((status&1)==1) || (status==0) ) status = SS$_ABORT; if ( next_buffer ) *next_buffer = (struct aio_buffer *) 0; return status; } buffer->next = (struct aio_buffer *) 0; pending_buf = stream->first; if ( pending_buf ) { /* I/O pending */ stream->last->next = buffer; } else { stream->first = buffer; } stream->last = buffer; if ( next_buffer ) { next_buf = free_buffer; if ( next_buf ) free_buffer = next_buf->next; } SYS$SETAST(1); /* * Start I/O if queue was previously empty. */ if ( !pending_buf ) { status = SYS$QIO ( 0, stream->channel, IO$_WRITEVBLK|IO$M_READERCHECK, &stream->iosb, aio_stream_ast, stream, stream->first->buffer, stream->first->length, 0, 0, 0, 0 ); if ( (status&1) == 0 ) { stream->abort = 1; if ( next_buf ) { SYS$SETAST(0); next_buf->next = free_buffer; free_buffer = next_buf; SYS$SETAST(1); *next_buffer = (struct aio_buffer *) 0; return status; } } } else { status = 1; } /* * Allocate next buffer if free list was empty and user requested one. */ if ( next_buffer ) { if ( !next_buf ) next_buf = allocate_aio_buffer(); *next_buffer = next_buf; } return status; } static void detach_aio_stream ( struct aio_stream *stream ) { int channel; SYS$SETAST(0); if ( stream->first ) { /* I/O pending */ channel = -1; stream->detached = 1; } else { /* No pending I/O, rundown directly */ channel = stream->channel; stream->next = free_stream; free_stream = stream; stream_count--; } SYS$SETAST(1); if ( channel != -1 ) SYS$DASSGN ( channel ); } static struct aio_stream *allocate_aio_stream ( int unit, int channel ) { struct aio_stream *stream; if ( stream_limit == 0 ) { /* * First call. */ int LIB$GETJPI(), code, status; code = JPI$_ASTLM; status = LIB$GETJPI ( &code, 0, 0, &stream_limit, 0, 0 ); stream_count = 0; stream_limit = stream_limit / 2; } /* * Allocate the structure, synchronize with AST layer. */ SYS$SETAST(0); while ( stream_count >= stream_limit ) { /* * No more streams available, wait for a detached stream to * rundown. */ stream_alloc_pending = 1; SYS$SETAST(1); SYS$HIBER(); SYS$SETAST(0); } stream_alloc_pending = 0; stream = free_stream; if ( stream ) { free_stream = stream->next; stream_count++; } SYS$SETAST(1); if ( !stream ) { /* * Allocate new set of stream structures, use the first and put * remaining 15 on free list. */ stream = (struct aio_stream *) malloc ( 16*sizeof(struct aio_stream) ); if ( stream ) { int i; for ( i = 1; i < 15; i++ ) stream[i].next = &stream[i+1]; SYS$SETAST(0); stream[15].next = free_stream; free_stream = &stream[1]; stream_count++; SYS$SETAST(1); } else return stream; } /* * Initialize the allocated block and return to caller. */ stream->next = (struct aio_stream *) 0; stream->total_count = 0; stream->channel = channel; stream->unit = unit; stream->abort = 0; stream->detached = 0; stream->first = (struct aio_buffer *) 0; stream->last = (struct aio_buffer *) 0; return stream; } /**************************************************************************/ /* Convert escaped characters in string to actual values. * * Arguments: * string Character string. Modified. * length Int. On input, original length of string. * On output, final length of unescaped string. */ static char * unescape_string ( char *string, int *length ) { int i, j, reslen, modified; /* * Scan string. */ for ( modified = reslen = i = 0; i < *length; i++ ) { if ( string[i] == '%' ) { /* * Escape seen, decode next 2 characters as hex and replace * all three with single byte. */ char value[4]; int val; value[0] = string[i+1]; value[1] = string[i+2]; value[2] = '\0'; i += 2; sscanf ( value, "%2x", &val ); if ( val > 127 ) val |= (-1 ^ 255); /* Sign extend */ string[reslen] = val; modified = 1; } else { /* Only copy bytes if escape edit took place. */ if ( modified ) string[reslen] = string[i]; } reslen++; } /* Return value is point to string editted. */ *length = reslen; return string; } /***************************************************************************/ /* * Define support routines for managing context list. */ static struct hpss_context *lookup_context ( int id ) { /* * Return context structure corresponding to the id argument. */ struct hpss_context *ctx; if ( id == 0 ) return (struct hpss_context *) 0; for ( ctx = context_list; ctx; ctx = ctx->next ) { if ( ctx->id == id ) break; } return ctx; } static struct hpss_context *create_context ( int channel, INTEGER context ) { /* * Allocate new context and give it new ID. */ struct hpss_context *ctx; ctx = (struct hpss_context *) malloc ( sizeof(struct hpss_context) ); if ( !ctx ) return ctx; if ( context_list ) { /* prepend to existing, first element will always have highest id */ ctx->id = context_list->id + 1; ctx->next = context_list; } else { /* Start new list */ ctx->id = 1; ctx->next = (struct hpss_context *) 0; } context_list = ctx; /* * Initialize the rest of the context. */ ctx->channel = channel; ctx->msglen = 0; /* no current message */ ctx->var.allocation = 30; ctx->var.size = 0; ctx->var.def = (struct sym_table_def *) malloc ( ctx->var.allocation*sizeof(struct sym_table_def) ); if ( !ctx->var.def ) ctx->var.allocation = 0; ctx->form.allocation = 0; /* only allocate as needed. */ ctx->form.size = 0; ctx->form.def = (struct sym_table_def *) 0; ctx->content_alloc = 0; ctx->content_used = 0; ctx->content_pos = 0; ctx->content = (char *) 0; /* * Return handle to caller. */ *context = ctx->id; return ctx; } /* * Ensure content region big enough to hold n addition bytes. */ static int check_content_allocation ( struct hpss_context *ctx, int extra ) { if ( (ctx->content_used + extra) > ctx->content_alloc ) { if ( ctx->content_alloc == 0 ) { /* initial allocation */ ctx->content_alloc = extra + 60000; ctx->content = malloc ( ctx->content_alloc ); } else { /* grow buffer */ ctx->content_alloc += (extra+120000); ctx->content = realloc ( ctx->content, ctx->content_alloc ); } } return 1; } /* * Read content. */ static int load_content_via_mailbox ( int mbx_unit, struct hpss_context *ctx ) { static char mbx_name[32]; static $DESCRIPTOR(mbx_name_dx,mbx_name); struct { unsigned short status, count; long pid; } iosb; int status, chan; /* * Assign channel to the mailbox, only read from it. */ sprintf ( mbx_name, "_MBA%d:", mbx_unit ); mbx_name_dx.dsc$w_length = strlen ( mbx_name ); chan = 0; status = SYS$ASSIGN ( &mbx_name_dx, &chan, 0, 0, CMB$M_READONLY ); if ( (status&1) == 0 ) return status; /* * Read data. */ for ( ; ; ) { check_content_allocation ( ctx, 4096 ); status = SYS$QIOW ( 0, chan, IO$_READVBLK|IO$M_WRITERCHECK, &iosb, 0, 0, &ctx->content[ctx->content_used], 4096, 0, 0, 0, 0 ); if ( (status&1)== 1 ) status = iosb.status; if ( (status&1) == 0 ) break; /* read error */ ctx->content_used += iosb.count; } /* * Rundown. */ SYS$DASSGN ( chan ); return status; } /* * Add ACL to mailbox to permitted named user to access. */ static int set_mailbox_access ( int channel, char *username ) { static $DESCRIPTOR(user_dx,""); static $DESCRIPTOR(security_class,"DEVICE"); int status, attrib; unsigned long uic; struct { short length, code; void *buffer; int *retlen;} item[2]; struct { unsigned char length, type; unsigned short flags; unsigned long access; unsigned long identifier; } ace; /* * Translate username to UIC. */ user_dx.dsc$w_length = strlen ( username ); user_dx.dsc$a_pointer = username; status = SYS$ASCTOID ( &user_dx, &uic, &attrib ); if ( (status&1) == 0 ) { fprintf(stderr,"Error %d translating identifier '%s' to UIC\n", status, username ); return status; } /* * Build ACE */ ace.type = ACE$C_KEYID; ace.length = sizeof(ace); ace.flags = 0; ace.access = 15; /* full access */ ace.identifier = uic; /* * Update the security profile. */ item[0].length = sizeof(ace); item[0].code = OSS$_ACL_ADD_ENTRY; item[0].buffer = (void *) &ace; item[0].retlen = (int *) 0; item[1].length = item[1].code = 0; status = SYS$SET_SECURITY ( &security_class, 0, &channel, 0, &item, 0, 0 ); if ( (status&1) == 0 ) { fprintf(stderr,"Error %d adding ACE to mailbox\n", status ); } return status; } /***************************************************************************/ /* Copy source string to destination string descriptor, padding with spaces. */ static void copy_string ( char *source, int slen, STRING dest, INTEGER dlen ) { int i, seg; seg = slen; if ( seg >= dest->dsc$w_length ) { /* String truncated */ seg = dest->dsc$w_length; memcpy ( dest->dsc$a_pointer, source, seg ); } else { /* String padded */ memcpy ( dest->dsc$a_pointer, source, seg ); memset ( &dest->dsc$a_pointer[seg], ' ', dest->dsc$w_length - seg ); } *dlen = seg; } /***************************************************************************/ /* Initialize context's cgi variable table from the message structure. * This function must be closely coordinated with the message encode function * of hpss_mst.c. */ static int expand_mailbox_message ( struct hpss_context *ctx, STRING subfunc ) { union hpss_mbxmsg *msg; int ndx, i, j, ndx_limit, dpos, status; char *cl_str; msg = &ctx->msg; i = 0; ndx_limit = ctx->msglen / sizeof(msg->def[0]); /* * Scan the table to find number of entries. */ for ( ndx = 2; msg->def[ndx].nlen > 0; ndx++ ) { if ( ndx >= ndx_limit ) { /* Ran off end of table */ return SS$_FORMAT; } } /* * Ensure enough entries in var table. */ if ( ndx > ctx->var.allocation ) { ctx->var.allocation = (ndx*2); ctx->var.def = (struct sym_table_def *) realloc ( ctx->var.def, ndx * sizeof(struct sym_table_def) ); } /* * Compute start of text area and copy subfunc argument if present. */ dpos = (ndx+1) * sizeof(msg->def[0]); /* current text position */ if ( subfunc ) { int sf_len; copy_string ( &msg->data[dpos], msg->comhdr.func, subfunc, &sf_len ); } dpos += (msg->comhdr.func+msg->comhdr.method); for ( j = 0, i = 2; i < ndx; i++ ) { /* * Initialize structure to point to data in message buffer. */ ctx->var.def[j].nlen = msg->def[i].nlen; ctx->var.def[j].vlen = msg->def[i].vlen; ctx->var.def[j].name = &msg->data[dpos]; dpos += (msg->def[i].nlen); ctx->var.def[j].value = &msg->data[dpos]; dpos += (msg->def[i].vlen); j++; } ctx->var.size = j; /* * See if content present in rest of message. */ if ( msg->def[ndx].vlen > 0 ) { /* * The content is stored between the end the CGI var text and * the end of buffer. */ if ( (msg->def[ndx].vlen + dpos) != ctx->msglen ) { fprintf(stderr, "BUGCHECK, content size mismatch with mailbox message EOB/n"); } if ( (msg->def[ndx].vlen + dpos) > ctx->msglen ) { msg->def[ndx].vlen = ctx->msglen - dpos; } status = check_content_allocation ( ctx, msg->def[ndx].vlen ); memcpy ( &ctx->content[ctx->content_used], &msg->data[dpos], msg->def[ndx].vlen ); ctx->content_used += msg->def[ndx].vlen; } /* * See if content to be sent via mailbox. */ if ( msg->comhdr.in_mbx != msg->comhdr.out_mbx ) { load_content_via_mailbox ( msg->comhdr.in_mbx, ctx ); } return 1; } /***************************************************************************/ /* * Initialize function creates a mailbox that the web server writes * service requests to. The service request is a single message that * contains: * Mailbox unit to recieve additional content (if needed). * Mailbox unit to write CGI response to. * prologue strings. * CGI variables. * First segment of content (as much as will fit in remainder of 8K message * limit). */ int hpss_initialize ( STRING mbx_logical, INTEGER mbx_type, /* 0-group table, 1-system table */ INTEGER context ) /* return value */ { int status, pflag, channel, i, nlen; struct { short length, code; char *buf; int *retlen; } item[4]; static char up_logical[512]; static $DESCRIPTOR(up_logical_dx,up_logical); char *access_user; /* * Fixup logical name, make all upper case and look for /access=user */ nlen = mbx_logical->dsc$w_length; if ( nlen > sizeof(up_logical) ) nlen = sizeof(up_logical); memcpy ( up_logical, mbx_logical->dsc$a_pointer, nlen ); up_logical_dx.dsc$w_length = nlen; for ( i = 0; i < nlen; i++ ) { up_logical[i] = toupper(up_logical[i]); if ( (up_logical[i] == '/') && (up_logical_dx.dsc$w_length == nlen) ) { up_logical_dx.dsc$w_length = i; } } access_user = (char *) 0; if ((up_logical_dx.dsc$w_length != nlen) && (nlen < sizeof(up_logical))) { /* * User supplied options. Only option recongized is /access= */ int list_length, j; char *olist, *ostr, *vstr, *next_opt; /* * Parse out next /option=value pair (This is not as robust as * a DCL parse - e.g no spaces allowed. */ list_length = nlen - up_logical_dx.dsc$w_length; up_logical[nlen] = '/'; /* sentinel */ olist = &up_logical[up_logical_dx.dsc$w_length+1]; ostr = olist; for ( i = 0; i < list_length; i++ ) if ( olist[i] == '/' ) { /* * Parse out =value if present, j ends with length of option. */ olist[i] = '\0'; vstr = (char *) 0; for ( j = 0; ostr[j]; j++ ) if ( ostr[j] == '=' ) { ostr[j] = '\0'; vstr = &ostr[j+1]; break; } /* * Interpret option. */ if ( j == 0 ) { fprintf(stderr, "Null mailbox option string ignored\n"); } else if ( 0 == strncmp(ostr,"ACCESS",j) ) { /* * /ACCESS=username option specified username of web * server that will be sending us requests. */ if ( !vstr || !(*vstr) ) { fprintf(stderr, "Missing username on ACCESS option\n"); } else { access_user = vstr; if ( *mbx_type == 0 ) fprintf ( stderr, "Warning, mailbox logical in GROUP table, %s.\n", "may not be visible to HTTP server" ); } } /* * Set up for next option. */ ostr = &olist[i+1]; } } /* * Temporarily define lnm$temporary_mailbox if needed so name goes into */ *context = 0; if ( *mbx_type == 0 ) { pflag = 0; item[0].length = strlen ( TEMP_MAILBOX_TABLE ); item[0].code = LNM$_STRING; item[0].buf = TEMP_MAILBOX_TABLE; item[0].retlen = (int *) 0; item[1].length = item[1].code = 0; /* terminate list */ status = SYS$CRELNM ( 0, &mbxlogical_table, &mbxlogical_destination, 0, item ); } else pflag = 1; /* * Create mailbox with specified logical name. Protections is * system:rwlp owner:rwlp group: world:wrlp */ channel = 0; status = SYS$CREMBX ( pflag, &channel, HPSS_MAX_MSGSIZE, HPSS_MAX_MSGSIZE, 0x0FF00, 0, &up_logical_dx, CMB$M_READONLY, 0 ); if ( (status&1) == 1 ) { /* * Mailbox successfully created add ACL if hpss_username */ if ( access_user ) set_mailbox_access ( channel, access_user ); if ( pflag == 1 ) { /* * Mark delete pending on mailbox so it effectively becomes * temporary. */ SYS$DELMBX ( channel ); /* make it goe away */ } } if ( pflag == 0 ) { /* * Remove logical used to force into group table. */ SYS$DELLNM ( &mbxlogical_table, &mbxlogical_destination, 0 ); } /* * Allocate context and return handle to it. */ if ( (status&1) == 1 ) { if ( !create_context(channel, context) ) status = SS$_ABORT; } return status; } /* * The accept function waits up to timeout seconds for the next message * to arrive and initialize the connext for calls to read/write/getenv/etc. * A timeout of zero means no timeout. * The disconnect function runs down the current request and prepares the * context for the next accept call. */ static void accept_timeout ( struct hpss_context *ctx ) { if ( ctx->accept_timer_active == 0 ) return; /* nothing to do */ ctx->accept_timer_fired = 1; SYS$CANCEL ( ctx->channel ); } int hpss_accept ( INTEGER context, INTEGER timeout, /* timeout in seconds, neg. for none */ INTEGER PID, /* process ID of web server */ STRING function ) /* script server invocation method */ { struct hpss_context *ctx; int flags, status, tsec; struct { unsigned short status, count; long pid; } iosb; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* invalid context */ /* * Read next message from mailbox. */ tsec = *timeout; if ( tsec <= 0 ) { /* * no timeout. */ /* flags = (tsec == 0) ? IO$M_WRITERCHECK | IO$M_NOW : 0; */ flags = 0; status = SYS$QIOW ( 0, ctx->channel, IO$_READVBLK|flags, &iosb, 0, 0, &ctx->msg, sizeof(ctx->msg), 0, 0, 0, 0 ); if ( status&1 ) status = iosb.status; } else { static long ticks_per_sec = -10000000; static long offset = 0; long interval[2]; /* * start async QIO. */ status = SYS$QIO ( 0, ctx->channel, IO$_READVBLK, &iosb, 0, 0, &ctx->msg, sizeof(ctx->msg), 0, 0, 0, 0 ); if ( (status&1) == 0 ) return status; /* * Start timer for timeout. */ LIB$EMUL ( &ticks_per_sec, timeout, &offset, interval ); ctx->accept_timer_active = 1; ctx->accept_timer_fired = 0; SYS$SETIMR ( 5, interval, accept_timeout, ctx, 0 ); /* * Wait for I/O to complete and convert cancel status to SS$_TIMEOUT. */ status = SYS$SYNCH ( 5, &iosb ); SYS$CANTIM ( ctx, 0 ); /* cancel timer */ SYS$SETAST ( 0 ); ctx->accept_timer_active = 0; if ( (ctx->accept_timer_fired == 1) && ((iosb.status&1)== 0) ) iosb.status = SS$_TIMEOUT; SYS$SETAST ( 1 ); status = iosb.status; } if ( (status&1) == 0 ) { ctx->outchan = -1; *PID = 0; return status; } *PID = iosb.pid; /* * Setup the context for processing the message, do sanity checks on size. */ ctx->msglen = iosb.count; if ( ctx->msglen < sizeof(ctx->msg.comhdr) ) { /* message too short */ } ctx->out_stream = (struct aio_stream *) 0; ctx->outbuf = (struct aio_buffer *) 0; ctx->content_used = 0; ctx->content_pos = 0; ctx->var.size = 0; ctx->form.size = 0; status = expand_mailbox_message ( ctx, function ); if ( status&1 ) { /* * Attempt to assign channel to output mailbox. */ static char mbxname[64]; static $DESCRIPTOR(mbxname_dx,mbxname); sprintf ( mbxname, "MBA%d:", ctx->msg.comhdr.out_mbx ); mbxname_dx.dsc$w_length = strlen ( mbxname ); ctx->outchan = 0; status = SYS$ASSIGN ( &mbxname_dx, &ctx->outchan, 0, 0, CMB$M_WRITEONLY ); if ( (status&1) == 0 ) fprintf(stderr, "Status of assign to outmbx '%s': %d\n", mbxname, status ); } else { ctx->outchan = -1; } /* * Allocate the output stream to the mailbox. */ if ( (status&1) == 1 ) { ctx->out_stream = allocate_aio_stream ( ctx->msg.comhdr.out_mbx, ctx->outchan ); if ( !ctx->out_stream ) { } ctx->outbuf = allocate_aio_buffer ( ); ctx->outbuf->length = 0; } return status; } int hpss_disconnect ( INTEGER context ) { struct hpss_context *ctx; struct { unsigned short status, count; long pid; } iosb; int status; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* invalid context */ /* * Run down the connection. */ if ( ctx->outchan == -1 ) return SS$_ABORT; ctx->msglen = 0; ctx->content_used = 0; if ( ctx->outbuf && (ctx->outbuf->length > 0) ) { /* * Flush final buffer. */ status = write_to_aio_stream ( ctx->outbuf, ctx->out_stream, (struct aio_buffer **) 0 ); } else if ( ctx->outbuf ) { /* * Free the io buffer. */ SYS$SETAST ( 0 ); ctx->outbuf->next = free_buffer; free_buffer = ctx->outbuf; SYS$SETAST ( 1 ); status = 1; } detach_aio_stream ( ctx->out_stream ); ctx->out_stream = (struct aio_stream *) 0; ctx->outbuf = (struct aio_buffer *) 0; return status; } /* * Build comma-separated list of names in a table. */ static int display_table ( struct sym_table *table, STRING value, INTEGER ret_length ) { struct sym_table_def *def; int i, j, vsize, length; char *out; def = table->def; vsize = value->dsc$w_length; out = value->dsc$a_pointer; length = 0; for ( i = 0; i < table->size; i++ ) { if ( length > 0 ) { if ( length >= vsize ) { *ret_length = vsize; return SS$_DATAOVERUN; } out[length++] = ','; } for ( j = 0; j < def[i].nlen; j++ ) { if ( length >= vsize ) { *ret_length = vsize; return SS$_DATAOVERUN; } out[length++] = def[i].name[j]; } } *ret_length = length; for ( i = length; i < vsize; i++ ) out[i] = ' '; /* pad with spaces */ return 1; } /* * The getenv function returns the value of a standard CGI variable * that was included in the initialze CGI message. */ static struct sym_table_def *table_search ( struct sym_table *table, char *name, int nlen ) { /* * internal function for scanning table for given name, returning * pointer to table entry or null. */ int i, size; struct sym_table_def *def; def = table->def; size = table->size; for ( i = 0; i < size; i++ ) { if ( def->nlen == nlen ) { if ( 0 == strncmp ( def->name, name, nlen ) ) return def; } def++; } return (struct sym_table_def *) 0; } int hpss_getenv ( INTEGER context, STRING name, /* CGI variable name (no WWW_ prefix)*/ STRING value, INTEGER ret_length ) { struct hpss_context *ctx; struct sym_table_def *def; int i; /* * Retrieve context from handle. */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* * Scan table. */ def = table_search (&ctx->var, name->dsc$a_pointer, name->dsc$w_length); if ( def ) { copy_string ( def->value, def->vlen, value, ret_length ); return 1; } /* * Search failed, see if it is special pseudo-variable %SYMBOL_LIST */ if ( name->dsc$w_length == 12 ) { if ( 0 == strncmp ( "%SYMBOL_LIST", name->dsc$a_pointer, 12 ) ) { /* * Return value is list of variables names in the table. */ return display_table ( &ctx->var, value, ret_length ); } } *ret_length = 0; return 0; } /* * The getform function references a table of name/value pairs, which it * initializes when needed by reading and decoding the request content. * Format of content: name=value&name=value... with escaping of special chars. */ static void parse_multipart_form ( struct hpss_context *ctx, char *clause, char *content, int content_length ) { int i, state; char *attr, *cptr, *bnd, *next; char boundary[512]; /* * Search clause string for boundary tag. */ attr = strtok(clause, ";"); /* * Scan content for boundary tags and build initial form table entries - * ctx->form[i].nlen and .name describes entire part. */ state = 0; /* * Convert the individual parts into a name and value */ for ( i = 0; i < ctx->form.size; i++ ) { } return; } static void parse_form_content ( struct hpss_context *ctx ) { char *var, *fdata; int status, table, i, j, k, vallen, count, ct_len, ce_len; int content_length, slist_len, length, prefix_len, LIB$SET_SYMBOL(); static $DESCRIPTOR(symbol,""); static $DESCRIPTOR(value,""); char *clause, content_type[256], content_encoding[64]; char sym_list[256], symname[256], symval[256]; struct sym_table_def *ctype, *cenc; /* * See if any content present first checking for POST data and using * query string as fallback. */ content_length = ctx->content_used; ctx->content_pos = ctx->content_used; ctype = table_search(&ctx->var, "CONTENT_TYPE", 12 ); clause = (char *) 0; if ( ctype ) { /* * ensure content type is application. */ ct_len = ctype->vlen; if ( ct_len > sizeof(content_type) ) ct_len = sizeof(content_type)-1; for ( i = 0; i < ct_len; i++ ) { if ( clause ) content_type[i] = ctype->value[i]; else { content_type[i] = toupper(ctype->value[i]); if ( content_type[i] == ';' ) clause = &content_type[i+1]; } } content_type[ct_len] = 0; } cenc = table_search(&ctx->var, "CONTENT_ENCODING", 16 ); if ( cenc ) { /* * Check for fancy types. */ } if ( content_length > 0 ) { /* * Ensure content type is one we can handle. */ if ( 0 == strncmp ( content_type, "MULTIPART/FORM-DATA", 19 ) ) { /* * Multi-part encoding. */ parse_multipart_form ( ctx, clause, ctx->content, ctx->content_used ); fprintf ( stderr, "Unsupported form content-type: '%s'\n", content_type ); return; } else if ( 0 != strncmp ( content_type, "APPLICATION/X-WWW-FORM-URLENCODED", 33 ) ) { /* * unsupported content type, give up. */ fdata = ""; length = 0; fprintf ( stderr, "Unsupported form content-type: '%s'\n", content_type ); } else { /* * Set to content passed to us by web server. */ fdata = ctx->content; length = ctx->content_used; } } else { /* * use QUERY_STRING variable if present. Note that the * string gets modified. */ struct sym_table_def *qstring, *method; qstring = table_search(&ctx->var, "QUERY_STRING", 12); method = table_search(&ctx->var, "REQUEST_METHOD", 14); if ( method ) { /* only use query string if method is GET */ if ( method->vlen != 3 ) qstring = (struct sym_table_def *) 0; else if ( strncmp ( "GET", method->value, 3 ) != 0 ) qstring = (struct sym_table_def *) 0; } else qstring = (struct sym_table_def *) 0; length = 0; if ( qstring ) { length = qstring->vlen; fdata = qstring->value; } } count = 0; if ( length > 0 ) { int start, finish, flen; /* * Parse the data. First split along '&' and then sub-divide * on the '=' into the name and value. */ if ( fdata[length-1] == '&' ) length = length -1; start = 0; finish = 0; for ( i = 0; i <= length; i++ ) if ( (fdata[i] == '&') || (i==length)) { /* * Value parsed. Unescape characters and look for first '=' * to delimit field name from value. */ flen = i - start; #ifndef KEEP_PLUSES for ( j = start; j < i; j++ ) if ( fdata[j] == '+' ) fdata[j] = ' '; #endif unescape_string ( &fdata[start], &flen ); finish = start + flen; /* fdata[start..finish] is full string */ for ( j = start; j < finish; j++ ) if ( fdata[j] == '=' ) { /* * fdata[start..j-1] is name, fdata[j+1..finish-1] is value. */ count++; if ( count > ctx->form.allocation ) { /* Expand the table */ if ( ctx->form.allocation == 0 ) { ctx->form.allocation = 20; ctx->form.def = (struct sym_table_def *) malloc ( sizeof(struct sym_table_def) * ctx->form.allocation ); } else { ctx->form.allocation += 20; ctx->form.def = (struct sym_table_def *) realloc ( ctx->form.def, sizeof(struct sym_table_def) * ctx->form.allocation ); } } ctx->form.def[ctx->form.size].nlen = j - start; ctx->form.def[ctx->form.size].vlen = finish - j-1; ctx->form.def[ctx->form.size].name = &fdata[start]; ctx->form.def[ctx->form.size].value = &fdata[j+1]; ctx->form.size++; break; } else { /* make field name upcase */ fdata[j] = _toupper(fdata[j]); if ( fdata[j] == '-' ) fdata[j] = '_'; } start = i+1; } } } int hpss_getform ( INTEGER context, STRING name, STRING value, INTEGER ret_length ) { struct hpss_context *ctx; struct sym_table_def *def; int i, nlen; char match_name[128]; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* invalid context */ /* * Build the form table if we haven't done so yet. */ if ( ctx->form.size == 0 ) { parse_form_content ( ctx ); } /* * upcase the caller's argument. */ nlen = name->dsc$w_length; if ( nlen >= sizeof(match_name) ) nlen = sizeof(match_name) - 1; for ( i = 0; i < nlen; i++ ) match_name[i] = _toupper(name->dsc$a_pointer[i]); match_name[nlen] = '\0'; /* * Scan table and return value to caller. */ def = table_search ( &ctx->form, match_name, nlen ); if ( def ) { copy_string ( def->value, def->vlen, value, ret_length ); return 1; } /* * Search failed, see if it is special pseudo-variable %SYMBOL_LIST */ if ( nlen == 12 ) { if ( 0 == strncmp ( "%SYMBOL_LIST", name->dsc$a_pointer, nlen ) ) { /* * Return value is list of variables names in the table. */ return display_table ( &ctx->form, value, ret_length ); } } *ret_length = 0; return 0; } /* * The read function reads the raw request content. This function is mutually * exclusive with the getform function. */ int hpss_read ( INTEGER context, STRING buffer, /* receives content. */ INTEGER ret_length ) /* number of bytes returned */ { struct hpss_context *ctx; int status, remainder, seg, ctl_flags, count; char *ptr; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* invalid context */ /* * Consume the bytes in the content block. */ if ( ctx->content_used > ctx->content_pos ) { count = ctx->content_used - ctx->content_pos; if ( count > buffer->dsc$w_length ) count = buffer->dsc$w_length; copy_string ( &ctx->content[ctx->content_pos], count, buffer, ret_length ); ctx->content_pos += count; } else { return SS$_ENDOFFILE; } return SS$_NORMAL; } /* * The write function sends CGI response data to the server. For * efficiency the data is bufferred until a disconnect or explicit flush flag * is set. The data path to the web server is treated as a byte stream, * you must set flag bit 0 if you want a record delimiter inserted. * * Flag bits: * 0 If set, append CRLF to end of data. * 1 If set flush buffer after appending current data. */ int hpss_write ( INTEGER context, STRING buffer, INTEGER flags ) { struct hpss_context *ctx; int status, remainder, seg, ctl_flags; struct { unsigned short status, count; long pid; } iosb; char *ptr; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) return SS$_BADPARAM; /* invalid context */ /* * write buffer in chunks. */ status = SS$_NORMAL; /* default return status */ ctl_flags = *flags; remainder = buffer->dsc$w_length; ptr = buffer->dsc$a_pointer; if ( (remainder == 0) && (ctl_flags&1) ) { /* null string with implied CRLF */ ptr = "\r\n"; remainder = 2; ctl_flags ^= 1; /* only do once. */ } for ( ; remainder > 0; remainder -= seg ) { /* * Move as much as we can into buffer. */ seg = sizeof(ctx->outbuf->buffer) - ctx->outbuf->length; if ( seg > remainder ) seg = remainder; memcpy ( &ctx->outbuf->buffer[ctx->outbuf->length], ptr, seg ); ptr += seg; ctx->outbuf->length += seg; /* * Flush buffer if now full. */ if ( ctx->outbuf->length >= sizeof(ctx->outbuf->buffer) ) { /* * Queue buffer to output stream and get new buffer. */ status = write_to_aio_stream ( ctx->outbuf, ctx->out_stream, &ctx->outbuf ); if ( ctx->outbuf ) ctx->outbuf->length = 0; if ( (status&1) == 0 ) return status; /* error */ } /* * Reset buffer to append CRLF if requested. */ if ( (remainder == seg) && (ctl_flags&1) ) { remainder = 2; seg = 0; ptr = "\r\n"; ctl_flags ^= 1; /* clear bit so we only do this once */ } } /* * See if caller requested a forced flush. */ if ( (ctl_flags&2) && (ctx->outbuf->length > 0) ) { status = write_to_aio_stream ( ctx->outbuf, ctx->out_stream, &ctx->outbuf ); if ( ctx->outbuf ) ctx->outbuf->length = 0; } return status; } /***************************************************************************/ /* Debug routine, format and write current state to sys$error * Flag bits: * 0 Show service mailbox message (encodes CGI symbol table and * content). * 1 Show loaded content (raw) * 2 Show CGI variable table. */ static void dump_table ( char *title, struct sym_table *table ); static void hex_dump ( char *title, char *data, int size ); int hpss_dump ( INTEGER context, INTEGER flags ) { struct hpss_context *ctx; int status, remainder, seg, ctl_flags; struct { unsigned short status, count; long pid; } iosb; char *ptr; /* * dereference the handle */ ctx = lookup_context ( *context ); if ( !ctx ) { fprintf(stderr,"hpss_dump called with bad context (addr=%x)\n",context); return SS$_BADPARAM; /* invalid context */ } ctl_flags = *flags; /* * Dump context block values. */ fprintf(stderr, "Context %d, dump:\n", ctx->id ); if ( ctx->msglen < sizeof(ctx->msg.comhdr) ) { fprintf ( stderr, " Warning, current message length too short\n"); } else { /* * Display the communication header data */ char subfunc[64], method[64], *text; int length, off, k; fprintf(stderr, " out mbx: MBA%d:, in mbx: MBA%d: (%s),", ctx->msg.comhdr.out_mbx, ctx->msg.comhdr.in_mbx, (ctx->msg.comhdr.out_mbx == ctx->msg.comhdr.in_mbx) ? "none" : "valid" ); text = (char *) 0; for ( k = 2; k < (ctx->msglen/4); k++ ) { if ( ctx->msg.def[k].nlen == 0 ) { text = &ctx->msg.data[(k+1)*sizeof(ctx->msg.def[0])]; } } if ( text ) { length = ctx->msg.comhdr.func; if ( length > 63 ) length = 63; strncpy ( subfunc, text, length ); subfunc[length] = '\0'; length = ctx->msg.comhdr.method; if ( length > 63 ) length = 63; strncpy ( method, &text[ctx->msg.comhdr.func], length ); method[length] = '\0'; fprintf(stderr, " Sub-func: '%s' (%d), method: '%s' (%d)\n", subfunc, ctx->msg.comhdr.func, method, ctx->msg.comhdr.method ); } else fprintf(stderr," invalid def table!\n" ); } if ( ctl_flags&1 ) { fprintf(stderr," current msglen = %d\n", ctx->msglen ); hex_dump ( " raw message buffer", ctx->msg.data, ctx->msglen ); } if ( ctl_flags&2 ) { fprintf(stderr," current content_used = %d, alloc=%d, pos=%d\n", ctx->content_used, ctx->content_alloc, ctx->content_pos ); if ( ctx->content_used > 0 ) hex_dump ( " raw content", ctx->content, ctx->content_used ); } if ( ctl_flags&4 ) { dump_table ( " CGI variable table", &ctx->var ); } if ( ctl_flags&8 ) { if ( ctx->form.size == 0 ) { parse_form_content ( ctx ); } if ( ctx->form.size == 0 ) fprintf(stderr, "No content to decode form data from\n"); else dump_table ( " Decoded form table", &ctx->form ); } return 1; } /* * list table entries. */ static void dump_table ( char *title, struct sym_table *table ) { int i, length; fprintf(stderr,"%s, alloc=%d entries, size=%d entries, addr: %x\n", title, table->allocation, table->size, table->def ); for ( i = 0; i < table->size; i++ ) { char sym_name[256], sym_val[2048]; length = table->def[i].nlen; if ( length >= sizeof(sym_name) ) length = sizeof(sym_name)-1; strncpy ( sym_name, table->def[i].name, length ); sym_name[length] = '\0'; length = table->def[i].vlen; if ( length >= sizeof(sym_val) ) length = sizeof(sym_val)-1; strncpy ( sym_val, table->def[i].value, length ); sym_val[length] = '\0'; fprintf ( stderr, " %s (%d) = '%s' (%d)\n", sym_name, table->def[i].nlen, sym_val, table->def[i].vlen ); } } /* * Display the contents of a buffer is hex form and ascii (displaying * non-printable chars to '.'. * * Line format: * 00 01 ... 16 "................" 0000 */ #define HX_START 52 static void hex_dump ( char *title, char *data, int size ) { char line[120]; int i, j, count, start; fprintf(stderr, "%s, %d bytes starting at %x\n", title, size, data ); for ( i = 0; i < size; i += 16 ) { memset ( line, ' ', 80 ); /* blank the line */ count = 16; /* number to show */ if ( (i+count) > size ) count = size-i; start = HX_START - (count*3); for ( j = i+count-1; j >= i; j-- ) { sprintf ( &line[start], " %02x", data[j] ); start += 3; } strcpy ( &line[HX_START], " \"................\"" ); for ( j = 0; j < count; j++ ) { if ( (data[i+j] >= ' ') && (data[i+j] <= '~') ) line[HX_START+2+j] = data[i+j]; } fprintf ( stderr, "%s %04x\n", line, i ); } }