/* * This CGI script demonstrates the use of multipart mixed/replace * content type to display a series of files automatically, pausing * between each picture. * * The CGI PATH_TRANSLATED variable is used as the name of a program * file that is opened and read. Each line of the program file specifies * a file to display. * * Date: 18-JAN-1996 * Revised: 30-JAN-1996 added source field. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cgilib.h" #include "scriptlib.h" #include "subaccess.h" #include "template.h" static int open_showfile ( char *file, int update_flag, subfield_ctx ctx ); static int do_show ( subfield_ctx ctx, char * ); static int show_show ( subfield_ctx ctx, char * ); static int do_post ( subfield_ctx, char *, char * ); /* * Make global definitions for database field names. */ static char *field_names = "seq,owner,content-type,file,delay,expiration,source"; static char *form_names[7] = { "seq", "owner", "content-type", "file", "delay", "expiration", "source" }; static char frm_seq[16], frm_owner[16], frm_content[128]; static char frm_file[256], frm_delay[16], frm_exp[32], frm_source[512]; static char *form_values[7] = { frm_seq, frm_owner, frm_content, frm_file, frm_delay, frm_exp, frm_source }; struct sf_params { char *method, *query; int state; int tok_count, tok_used; int tok_inwhite; char *tok[10]; char tok_buf[1024]; }; static int my_out ( struct sf_params *arg, char *buf, int length ); static int my_escape ( struct sf_params *arg, char *buf, int length ); static long elev_privs[2]; int SYS$SETPRV(); /*****************************************************************************/ int main ( int argc, char ** argv ) { int status, need_update; long prev_privs[2]; char *method, *template, *showfile, *query, *user_agent; struct subfield_context sfctx; struct sf_params escape_arg; /* * Kill installed image privileges. */ elev_privs[0] = PRV$M_SYSPRV; elev_privs[1] = 0; status = SYS$SETPRV ( 0, elev_privs, 0, prev_privs ); printf("disable priv status: %d, prev: %d\n", status, prev_privs[0]&elev_privs[0] ); /* * Establish CGI environment and retrieve method and path_info. */ status = cgi_init ( argc, argv ); if ( (status&1) == 0 ) exit ( status ); method = cgi_info ( "REQUEST_METHOD" ); if ( !method ) method = ""; escape_arg.method = method; template = cgi_info ( "PATH_TRANSLATED" ); if ( !template ) { cgi_printf ( "content-type: text/plain\n" ); cgi_printf ( "status: 500 bad info\n\nMissing or invalid path info\n"); cgi_printf ( "Must supply template file name\n"); exit ( 1 ); } if ( !*template ) { cgi_printf ( "content-type: text/plain\n" ); cgi_printf ( "status: 500 bad info\n\nNull or invalid path info\n"); cgi_printf ( "Must supply template file name\n"); exit ( 1 ); } showfile = ""; /* * Determine query string, if present then preload data */ set_default_template_fields ( 5, form_names, form_values ); frm_file[0] = frm_seq[0] = frm_owner[0] = frm_content[0] = '\0'; frm_delay[0] = frm_exp[0] = frm_source[0] = '\0'; query = cgi_info ( "QUERY_STRING" ); if ( !query ) query = ""; /* * Parse template. */ escape_arg.state = 0; escape_arg.query = query; status = process_template_file ( template, (template_callback) my_out, &escape_arg, (template_callback) my_escape, &escape_arg ); if ( escape_arg.state < 2 ) { /* * Fallback error message. */ cgi_printf ( "content-type: text/plain\n" ); cgi_printf ( "status: 500 error\n\n" ); cgi_printf ( "Error processing template file, final status: %d\n", status ); return 1; } /* * Dispatch on method. */ showfile = escape_arg.tok[0]; if ( 0 == strcmp ( method, "GET" ) && !(*query) ) { /* * Do server push. */ user_agent = cgi_info ( "HTTP_USER_AGENT" ); if ( user_agent ) if ( strncmp ( user_agent, "NCSA Mosaic for the X Window System/2.0", 39 ) == 0 ) { cgi_printf("Content-type: text/plain\nStatus: 500 bad client\n\n"); cgi_printf("Sorry, your client does not support multipart output\n"); exit(1); } /* * Opend the show list database. */ if ( !*showfile ) { cgi_printf ( "Content-type: text/plain\nStatus: 403 bad file\n\n"); cgi_printf("Bad file: '%s'\n", cgi_info ( "PATH_INFO" ) ); exit ( 1 ); } status = open_showfile ( showfile, 0, &sfctx ); if ( (status&1) == 0 ) { cgi_printf ( "Content-type: text/plain\nStatus: 404 bad file\n\n"); cgi_printf("Error reading show file '%s'\n", cgi_info ( "PATH_INFO" ) ); exit ( 1 ); } /* * Start the show. */ status = do_show ( &sfctx, showfile ); } else if ( 0 == strcmp ( method, "GET" ) ) { /* * Template parse handled display of form. */ status = 1; } else if ( 0 == strcmp ( method, "HEAD" ) ) { /* * Make head request return success. */ cgi_printf ( "Content-type: text/plain\n\n"); /* I'm alive */ } else if ( 0 == strcmp ( method, "POST" ) ) { /* * Handle form input to edit the showfile, creating a new * incarnation. */ if ( !*showfile ) { cgi_printf ( "Content-type: text/plain\nStatus: 403 bad file\n\n"); cgi_printf("Bad file: '%s'\n", cgi_info ( "PATH_INFO" ) ); exit ( 1 ); } status = open_showfile ( showfile, 1, &sfctx ); if ( (status&1) == 0 ) { cgi_printf ( "Content-type: text/plain\nStatus: 404 bad file\n\n"); cgi_printf("Error updating show file '%s'\n", cgi_info ( "PATH_INFO" ) ); exit ( 1 ); } status = do_post ( &sfctx, template, showfile ); } else { /* * Invalid method, return error. */ cgi_printf ( "Content-type: text/plain\n" ); cgi_printf ( "Status: 400 bad request\n\n Invalid method\n" ); } return status; } /*************************************************************************/ /* * Open showfile with a 'create-if' option if update flag set. */ static char *fdl_string = "\ FILE; ORGANIZATION indexed; PROTECTION (system:RWED,owner:RWED,group,world);\ RECORD; CARRIAGE_CONTROL carriage_return; FORMAT variable; SIZE 8192;\ KEY 0; CHANGES no; PROLOG 3; SEG0_LENGTH 8; SEG0_POSITION 0; TYPE string;\ KEY 1; CHANGES yes; DUPLICATES yes; SEG0_LENGTH 12; SEG0_POSITION 9; \ TYPE string;\ "; static int open_showfile ( char *file, int update_flag, subfield_ctx ctx ) { int status, i, j, scount, last_dot; /* * attempt to open indexed file. */ status = subfield_open ( file, update_flag ? "r+" : "r", 4096, ctx ); if ( ((status&1) == 0) && update_flag ) { /* * Open failure, attempt to create new file (only attempt if * open for update specified. */ IFILE *tmp; char head_rec[512]; status = ifdlcreate ( fdl_string, file, "sys$disk:[]" ); if ( (status&1) == 0 ) return status; /* * Initialize header record so that subfield_open can determine * what the fields names are and which are index keys (fixed length). */ sprintf ( head_rec, "%8s%c%12s%c%s%c%s", "", 0, "", 0, field_names, 0, "0" ); tmp = ifopen ( file, "r+" ); ifwrite_rec ( head_rec, 24 + 1 + strlen(field_names), tmp ); ifclose ( tmp ); /* * Re-attempt subfield open and take whatever the result. */ status = subfield_open ( file, "r+", 4096, ctx ); } return status; } /*************************************************************************/ /* Process show file sf, each line holds a filename and flags controlling * display of the file. */ static int do_show ( subfield_ctx ctx, char *showfile ) { int status, length, need_update, bnd_len, i, j, count, max_count; FILE *df; /* display file */ char *boundary_tag, *line; char control_line[2048], display_buffer[2048]; /* * Check client software for known varieties that don't support server push. */ /* * Set content to multipart/mixed-replace */ boundary_tag = "-azzadfiwerowg\n"; /* note final linefeed */ bnd_len = strlen ( boundary_tag ); cgi_printf ( "Content-type: multipart/x-mixed-replace;boundary=%s\n", boundary_tag ); cgi_printf ( "%s", boundary_tag ); /* initial boundary */ /* * Get count. */ max_count = atoi ( ctx->hdr_data ); /* * Outer loop scans show file. */ for ( count = i = 0; i < max_count; count++ ) { /* * Get next record from file */ char *seq, *owner, *content_type, *url, *delay, *expiration; status = subfield_read ( ctx, "", 0, 0 ); if ( (status&1) == 0 ) { status = subfield_close ( ctx ); if ( (status&1) == 0 ) break; if ( count == 0 ) break; /* empty file */ status = open_showfile ( showfile, 0, ctx ); if ( (status&1) == 0 ) break; continue; } /* * Skip if record expired. */ expiration = subfield_value ( ctx, "expiration" ); if ( !expiration ) expiration = ""; if ( *expiration ) { int exp_day, cur_day, LIB$DAY(); LIB$DAY ( &cur_day, 0, 0 ); exp_day = atoi ( expiration ); if ( exp_day < cur_day ) { /* Don't count as valid record */ --count; continue; } } /* * Interpret line and output next part. */ url = subfield_value ( ctx, "file" ); df = fopen ( url, "r", "mbc=32" ); if ( !df ) { cgi_printf ( "Content-type: text/plain\n\n" ); cgi_printf ( "Error openning '%s'\n", url ); } else { content_type = subfield_value ( ctx, "content-type" ); if ( !content_type ) content_type = "text/plain"; if ( !*content_type ) content_type = "text/plain"; cgi_printf ( "Content-type: %s\n\n", content_type ); for ( ; ; ) { line = fgets ( display_buffer, sizeof(display_buffer), df ); if ( !line ) break; cgi_printf ( "%s", line ); } fclose ( df ); } /* * Send to boundary tag to mark part completed. Use * low-level scriptlib routine to force flush of buffer. * Also check status and abort if client closed connection. */ status = net_link_write ( boundary_tag, bnd_len ); if ( (status&1) == 0 ) { fprintf(stderr,"Error writing boundary tag" ); return 1; /* broken connection */ } delay = subfield_value ( ctx, "delay" ); sleep ( delay ? atoi(delay) : 5 ); } /* * Send end-of-parts message, append "--" to boundary tag. */ strcpy ( display_buffer, boundary_tag ); strcpy ( &display_buffer[bnd_len-1], "--" ); cgi_printf ( "Content-type: text/plain\n\nEnd of show!\n%s\n", display_buffer ); return 1; } /*************************************************************************/ /* Retrieve ACL on indicated filename and see if it permits read access * to specified UIC. */ static int check_access ( char *fname, char *user ) { struct { short len, code; char *buffer; int *retlen; } itemlist[10]; #define SETITEM(itm,a,b,c,d) \ itm.len=a; itm.code=b; itm.buffer=(char *) c; itm.retlen=(int *) d; int status, acllen, context, objtype, i, j, slash, SYS$CHECK_ACCESS(); long access, rights[2], privs[2]; char vmsfile[256]; $DESCRIPTOR(username,""); $DESCRIPTOR(filename,""); /* * Convert filename to VMS syntax. */ if ( fname[0] == '/' ) { slash = 0; for ( j = i = 1; fname[i]; i++ ) { vmsfile[i-j] = fname[i]; if ( fname[i] == '/' ) { if ( slash == 0 ) { vmsfile[i-j] = ':'; vmsfile[i] = '['; j = 0; } else { vmsfile[i] = '.'; } slash = i; } if ( i > 254 ) break; } vmsfile[i-j] = '\0'; if ( slash ) { if ( vmsfile[slash] == '[' ) strcpy ( &vmsfile[slash], &fname[slash+1] ); else vmsfile[slash] = ']'; } } else { strncpy ( vmsfile, fname, 255 ); vmsfile[255] = '\0'; } /* * Setup arguments and call $CHECK_ACCESS */ access = ARM$M_READ; SETITEM(itemlist[0],4,CHP$_ACCESS,&access,0) itemlist[1].len = itemlist[1].code = 0; objtype = ACL$C_FILE; context = 0; filename.dsc$w_length = strlen ( vmsfile ); filename.dsc$a_pointer = vmsfile; username.dsc$w_length = strlen ( user ); username.dsc$a_pointer = user; status = SYS$SETPRV ( 1, elev_privs, 0, 0 ); printf("Enable priv status: %d\n", status ); status = SYS$CHECK_ACCESS ( &objtype, &filename, &username, &itemlist ); SYS$SETPRV ( 0, elev_privs, 0, 0 ); if ( (status&1) == 0 ) return status; return status; } /****************************************************************************/ /* Read form content and parse. Return value is number of keywords parsed, * -1 for error. */ static int parse_form ( int key_limit, char **key, char **value ) { int i, j, content_length, length, count, start, finish, flen; char *var, *fdata; /* * Read form content into memory in preparation for parsing. */ count = 0; var = cgi_info ( "CONTENT_LENGTH" ); content_length = var ? atoi(var) : 0; if ( content_length > 0 ) { /* * Allocate buffer and read entire form data into it, forcing final &. */ fdata = malloc ( content_length+1 ); if ( !fdata ) return -1; length = cgi_read ( fdata, content_length ); } else { /* See if form method was GET by mistake. */ var = cgi_info ( "QUERY_STRING" ); if ( var ) { length = strlen ( var ); fdata = malloc ( length + 1 ); if ( !fdata ) return -1; strcpy ( fdata, var ); } else length = 0; } if ( length <= 0 ) return length; /* * Parse into keyword/value pairs, ampersands delimit the pairs. */ if ( fdata[length-1] != '&' ) fdata[length++] = '&'; start = finish = 0; for ( i = 0; i < length; i++ ) if ( !fdata[i] || (fdata[i] == '&') ) { /* * Value parsed. Unescape string and look for first equals sign * to delimit field name from value. Convert pluses to spaces. */ flen = i - start; for ( j = start; j < i; j++ ) if ( fdata[j] == '+' ) fdata[j] = ' '; net_unescape_string ( &fdata[start], &flen ); finish = start + flen; fdata[finish] = '\0'; for ( j = start; j < finish; j++ ) if ( fdata[j] == '=' ) { /* Save pointers to pair in callers buffer, terminate strings */ key[count] = &fdata[start]; fdata[j] = '\0'; value[count] = &fdata[j+1]; count++; if ( count >= key_limit ) return count; break; } else { /* Make characters in field name upper case */ fdata[j] = _toupper(fdata[j]); } start = i + 1; } return count; } /****************************************************************************/ /* Update header data field. */ int increment_header ( subfield_ctx ctx ) { int num; num = atoi ( ctx->hdr_data ); sprintf ( ctx->hdr_data, "%d", num + 1 ); return 1; } /****************************************************************************/ /* Post method is used to submit a form */ static int do_post ( subfield_ctx ctx, char *template, char *showfile ) { long rem_uic, f_uic; int i, status, length, key_count; int msg_ndx, mtype_ndx, dly_ndx, exp_ndx, del_ndx, src_ndx; char *remote_user, *keyword[20], *value[20], new_file[256], ctype[256]; FILE *newsf; char prev_hdr[32], *space, *line, **elem; /* * Parse form data and save indices. */ key_count = parse_form ( 20, keyword, value ); msg_ndx = mtype_ndx = dly_ndx = exp_ndx = del_ndx = src_ndx = -1; for ( i = 0; i < key_count; i++ ) { if ( strcmp ( keyword[i], "MESSAGE_TYPE" ) == 0 ) mtype_ndx = i; else if ( strcmp ( keyword[i], "MESSAGE" ) == 0 ) msg_ndx = i; else if ( strcmp ( keyword[i], "DELAY" ) == 0 ) dly_ndx = i; else if ( strcmp ( keyword[i], "EXPIRATION" ) == 0 ) exp_ndx = i; else if ( strcmp ( keyword[i], "DELETE_ID" ) == 0 ) del_ndx = i; } /* * Convert username of poster to UIC for ACL testing. */ remote_user = cgi_info ( "REMOTE_USER" ); if ( !remote_user ) { remote_user = "WWW_SERVER"; } status = check_access ( template, remote_user ); if ( (status&1) == 0 ) { cgi_printf ( "Content-type: text/plain\n\n" ); cgi_printf ( "No permission to post to '%s'.\nReason: ", template ); if ( status == SS$_NOPRIV ) cgi_printf ( "no access\n" ); else if ( status == RMS$_RNF ) cgi_printf ( "unknown user\n"); else cgi_printf ( "status=%d\n", status ); return 1; } /* * Check for delete request. */ if ( del_ndx >= 0 ) { char *rec_owner, *rec_file; /* * Read record and verify owner. */ cgi_printf("Content-type: text/plain\n\n" ); status = subfield_read ( ctx, value[del_ndx], 0, strlen(value[del_ndx]) ); if ( (status&1) == 0 ) { cgi_printf("Error reading record %s\n", value[del_ndx] ); } else { rec_owner = subfield_value ( ctx, "owner" ); if ( strcmp ( rec_owner, remote_user ) != 0 ) { cgi_printf("Not owner of record %s\n", value[del_ndx] ); } else { /* * Onwer checks out, delete record. */ rec_file = subfield_value ( ctx, "file" ); status = subfield_delete_rec ( ctx ); if ( (status&1) == 0 ) { cgi_printf("Error deleting record %s: %d\n", value[del_ndx], status ); } else { cgi_printf("Record %s deleted\n", value[del_ndx] ); } /* * Remove file. */ if ( rec_file ) { if ( 0 == delete ( rec_file ) ) { cgi_printf("Deleted file %s\n", rec_file ); } else { cgi_printf("Error deleting %s\n", rec_file ); } } else { cgi_printf("No file specified in record\n"); } } } return status; } /* * Get record number to add. */ status = subfield_update_header ( ctx, increment_header ); /* * build new record. */ subfield_set ( ctx, "seq", ctx->hdr_data ); subfield_set ( ctx, "owner", remote_user ); subfield_set ( ctx, "content-type", "text/plain" ); subfield_set ( ctx, "file", msg_ndx >= 0 ? value[msg_ndx] : "" ); if ( dly_ndx < 0 ) { subfield_set ( ctx, "delay", "5" ); } else { int delay_val; char delay_str[8]; delay_val = atoi(value[dly_ndx]); if ( delay_val < 1 ) delay_val = 5; else if ( delay_val > 30 ) delay_val = 30; sprintf ( delay_str, "%d", delay_val ); subfield_set ( ctx, "delay", delay_str ); } if ( exp_ndx < 0 ) { subfield_set ( ctx, "expiration", "" ); } else { int LIB$DAY(), cur_day, delta; char exp_value[32]; LIB$DAY ( &cur_day, 0, 0 ); delta = atoi ( value[exp_ndx] ); sprintf ( exp_value, "%d", cur_day + delta ); subfield_set ( ctx, "expiration", exp_value ); } subfield_set ( ctx, "source", msg_ndx >= 0 ? value[msg_ndx] : "" ); if ( (mtype_ndx >= 0) && (msg_ndx >= 0) ) { /* * Make new messagefile. */ int copy_message( char * user, int text_flag, char *arg, char *showfile, char *new, char *content_type ); status = copy_message ( remote_user, strcmp(value[mtype_ndx],"URL"), value[msg_ndx], showfile, new_file, ctype ); src_ndx = msg_ndx; subfield_set ( ctx, "file", new_file ); subfield_set ( ctx, "content-type", ctype ); } /* * Add record. */ status = subfield_write ( ctx ); /* * Report status. */ cgi_printf ( "Content-type: text/plain\n" ); cgi_printf ( "\n" ); if ( (status&1) == 1 ) { cgi_printf ( "Added record to slideshow %s, id: %s\n", showfile, ctx->hdr_data ); cgi_printf ( "Content file: %s\n", new_file ); cgi_printf ( "Content type: %s\n", ctype ); #ifdef DEBUG cgi_printf ( "Form fields: \n" ); for ( i = 0; i < key_count; i++ ) cgi_printf(" %s = '%s'\n", keyword[i], value[i] ); #endif } else { cgi_printf ( "Error updating slideshow %s, status: %d\n", showfile, status ); } return 1; } static int my_out ( struct sf_params *arg, char *buf, int length ) { int sent, status; /* * Ignore all received data until my_escape doen parsing file info. */ if ( arg->state < 2 ) return 1; /* * Check if query defined. */ if ( arg->state == 2 ) return 0; buf[length] = '\0'; cgi_printf ( "%s", buf, length ); return 1; } static int my_escape ( struct sf_params *arg, char *buf, int length ) { int i, j, sent, status, subfield_count, content_length; char *var; struct subfield_context subfield; /* * collect data until we have enough to process. */ if ( arg->state == 0 ) { arg->tok_count = 0; arg->tok_used = 0; arg->tok_inwhite = 1; arg->state = 1; } else if ( arg->state == 3 ) { if ( buf[length] ) { char save_c; save_c = buf[length]; buf[length] = '\0'; status = cgi_printf("%s",buf); buf[length] = save_c; } else { status = cgi_printf("%s",buf); } if ( (status&1) == 0 ) return status; } if ( arg->state > 1 ) return 1; /* ignore rest */ /* * Parse next string out of escape infor. */ for ( i = 0; i < length; i++ ) { if ( arg->tok_used >= sizeof ( arg->tok_buf ) ) break; if ( arg->tok_inwhite ) { if ( !isspace ( buf[i] ) ) { arg->tok_inwhite = 0; arg->tok[arg->tok_count] = &arg->tok_buf[arg->tok_used]; arg->tok_buf[arg->tok_used++] = buf[i]; } } else { if ( isspace ( buf[i] ) ) { arg->tok_inwhite = 1; arg->tok_buf[arg->tok_used++] = '\0'; arg->tok_count++; if ( arg->tok_count >= 3 ) { arg->state = 2; break; } } else { arg->tok_buf[arg->tok_used++] = buf[i]; } } } /* * If arg-state reaches 2 we have all we need. */ if ( arg->state == 2 ) { /* * Determine operating mode */ printf("escape done, method: '%s', query: '%s'\n", arg->method, arg->query ); if ( strcmp(arg->method, "GET") == 0 ) { int q_val; if ( *arg->query ) { /* * Set state to force return of template data to client, */ arg->state = 3; q_val = *arg->query ? atoi(arg->query) : 0; if ( q_val ) { /* * Pre-load field values. */ /* status = load_fields ( arg ); if ( (status&1) == 0 ) return status; */ } cgi_printf("content-type: text/html\nstatus: 200 gotcha\n\n"); return 1; } } else if ( strcmp(arg->method, "POST") == 0 ) { } return 998; } return 1; }