From 316de23144f644dfa82802b3f9333bae567195cb Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 5 Jul 2002 05:33:40 +0000 Subject: [PATCH] Added upload authentication svn path=/trunk/boinc/; revision=162 --- Makefile.in | 44 +++---- TODO | 14 ++- checkin_notes | 111 ++++++++++++++++++ client/Makefile.in | 2 + client/client_state.C | 4 +- client/client_types.C | 91 +++++++++++++-- client/client_types.h | 13 ++- client/cs_files.C | 16 +-- client/file_names.C | 11 +- client/file_xfer.C | 33 ++++++ client/file_xfer.h | 20 +++- client/http.C | 137 +++++++++++++++------- client/http.h | 31 +++-- client/main.C | 1 + client/scheduler_op.C | 6 +- client/test_file_xfer.C | 73 ++++++++---- db/mysql_util.C | 2 +- doc/index.html | 6 +- doc/intro.html | 10 +- doc/master_url.html | 27 +++++ doc/tools_security.html | 25 ++++ html/user/index.html | 4 + lib/Makefile.in | 6 +- lib/crypt.C | 97 ++++++++++++--- lib/crypt.h | 17 +++ lib/crypt_prog.C | 5 +- lib/md5_file.C | 15 +++ lib/md5_file.h | 4 + lib/parse.C | 15 ++- lib/parse.h | 1 + sched/Makefile.in | 24 +++- sched/file_upload_handler.C | 195 +++++++++++++++++++++++++++++-- sched/server_types.C | 5 +- test/1sec_result | 3 +- test/concat_result | 3 +- test/init.inc | 22 +++- test/master.html | 3 + test/sah_result | 3 +- test/test_1sec.php | 1 + test/test_concat.php | 1 + test/test_dynamic.php | 1 + test/test_max_water_prefs.php | 1 + test/test_min_water_prefs.php | 1 + test/test_normal_water_prefs.php | 1 + test/test_prefs.php | 1 + test/test_projects.php | 1 + test/test_stderr.php | 1 + test/test_uc.php | 1 + test/test_uc_slow.php | 1 + test/uc_result | 3 +- test/ucs_result | 3 +- tools/Makefile.in | 10 +- tools/add.C | 2 +- tools/backend_lib.C | 45 +++++-- tools/backend_lib.h | 12 +- tools/create_work.C | 56 +++++++-- tools/process_result_template.C | 135 +++++++++++++-------- 57 files changed, 1121 insertions(+), 255 deletions(-) create mode 100644 doc/master_url.html create mode 100644 doc/tools_security.html create mode 100644 test/master.html diff --git a/Makefile.in b/Makefile.in index bbf0bd18d5..af788060c4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -10,34 +10,34 @@ VPATH = @srcdir@ # This needs to be fixed # all: - cd db; gmake; cd ..; \ - cd RSAEuro/source/; gmake; cd ../../; \ - cd lib; gmake; cd ..; \ - cd api; gmake; cd ..; \ - cd apps; gmake; cd ..; \ - cd client; ../$(srcdir)/client/configure; gmake; cd ..; \ - cd sched; gmake; cd ..; \ - cd tools; gmake; cd ..; + cd RSAEuro/source; make; cd ../..; \ + cd db; make; cd ..; \ + cd lib; make; cd ..; \ + cd api; make; cd ..; \ + cd apps; make; cd ..; \ + cd client; ../$(srcdir)/client/configure; make; cd ..; \ + cd sched; make; cd ..; \ + cd tools; make; cd ..; clean: rm -f boinc.tar.gz config.cache; \ - cd db; gmake clean; cd ..; \ - cd RSAEuro/source/; gmake clean; cd ../../; \ - cd lib; gmake clean; cd ..; \ - cd api; gmake clean; cd ..; \ - cd apps; gmake clean; cd ..; \ - cd client; gmake clean; cd ..; \ - cd sched; gmake clean; cd ..; \ - cd tools; gmake clean; cd ..; + cd RSAEuro/source/; make clean; cd ../../; \ + cd db; make clean; cd ..; \ + cd lib; make clean; cd ..; \ + cd api; make clean; cd ..; \ + cd apps; make clean; cd ..; \ + cd client; make clean; cd ..; \ + cd sched; make clean; cd ..; \ + cd tools; make clean; cd ..; install: all -mkdir -p /usr/local/boinc; - cd sched; gmake install; - cd lib; gmake install; - cd api; gmake install; - cd apps; gmake install; - cd client; gmake install; - cd tools; gmake install; + cd sched; make install; + cd lib; make install; + cd api; make install; + cd apps; make install; + cd client; make install; + cd tools; make install; cp -r test /usr/local/boinc; tar: clean diff --git a/TODO b/TODO index e84426e959..d415ef8006 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,14 @@ HIGH-PRIORITY (must be done to support SETI@home) -- Code-signing (David) - research tools for code-signing +- Code-signing + In progress - David - Upload authentication (David) Each result contains a "certificate", signed with project key, giving - list of: file name, max size - min, max times to xfer modify put program to decrypt certificate, enforce name/size/time limits + In progress - David - Network retry policies (Eric?) can't download file: when to give up? how to retry? @@ -16,18 +17,24 @@ HIGH-PRIORITY (must be done to support SETI@home) can't connect to sched server error return from sched server -- proxy support (Open Source) +- make scheduling server use fast CGI + In progress - Michael + +- proxy support HTTP, Socks Look at other open source code - team system (Barry) in PHP + In progress - Barry - credit display (Barry) in PHP + In progress - Barry - CPU accounting in the presence of checkpoint/restart (Michael) core client periodically gets CPU time, accumulates in state file + In progress - Michael - test versioning mechanisms for core Idea: need to notify user if core becomes out of date. @@ -45,6 +52,7 @@ HIGH-PRIORITY (must be done to support SETI@home) - initialize rsc_fpops and rsc_iops in client WORKUNIT - check server sends correct number of work units - check client requests correct number of seconds of work + In progress - Michael - measure hardware parameters: CPU speed, #CPUs, memory, disk - define CPU benchmarks diff --git a/checkin_notes b/checkin_notes index 60f4139b63..7da3a8210c 100755 --- a/checkin_notes +++ b/checkin_notes @@ -575,6 +575,18 @@ David A June 20 2002 prefs1.xml, prefs2.xml (new) test_*.php +David A June 21 2002 + - top-level Makefile now compiles RSAEuro/, + and doesn't refer to sched_fcgi + - Added element to html_user/index.html, + making it the "master file" for test project. + This file must be placed in the directory referred to by + http://localhost/ + + Makefile.in + html_user/ + index.html + Michael Gary June 21 2002 - added install to the make system to put executables in /usr/local/boinc @@ -732,4 +744,103 @@ Michael Gary 7/03/2002 configure configure.in +David Anderson July 4, 2002 + - Added support for upload authentication. + This prevents bad guys from filling up data servers with trash. + In this scheme, each element sent from + server to client includes a field limiting + the size of the file, and includes a digital signature + based on the project's "upload authentication" key pair. + File uploads, instead of being done by PUT, are now done by POST + to a CGI program, "file_upload_handler". + The request header includes the signed , + and the CGI program verifies the signature and enforces the size limit. + + The affected pieces of code: + - Added a function create_keys() in PHP test scripts + to create encryption keys. Call it from all script. + - Added environment var BOINC_KEY_DIR saying where keys are kept. + - The client must maintain an exact copy of each XML, + and of the signature, so that it can send to upload server. + - Added a new variant of HTTP operation, HTTP_OP_POST2. + The existing variants all use single files for request and reply. + The new variant (used for file upload) has a request + consisting of a memory block followed by (part of) a file; + the reply is in memory. + This avoid copying possibly huge upload files. + - FILE_XFER objects now take a FILE_INFO as initialization argument; + needed to convey authentication info. + The upload variant creates and sends the authentication header. + - Result templates now include a in each + element, and the URLs refer to the + file_upload_handler (with no filename) + - process_result_template() works differently, since it must + generate a digital signature at the end of each + - create_work expects the name of a private key file. + - Added crypt/md5 functions to sign/verify in memory, + encode/decode ASCII data in memory, checksum in memory + - Change "gmake" to "make" in top-level makefile. + (alias make to gmake if this is a problem) + + boinc/ + Makefile.in + TODO + RSAEuro/source/ + rsaeuro.h + client/ + Makefile.in + client_state.C + client_types.C,h + cs_files.C + file_names.C + file_xfer.C,h + http.C,h + main.C + scheduler_op.C + test_file_xfer.C + db/ + mysql_util.C + doc/ + index.html + intro.html + master_url.html (new) + project_startup.html (new) + tools_security.html (new) + html_user/ + index.html + lib/ + Makefile.in + crypt.C,h + crypt_prog.C + md5_file.C,h + parse.C,h + sched/ + Makefile.in + file_upload_handler.C + server_types.C + test/ + 1sec_result + concat_result + init.inc + master.html (new) + sah_result + test_1sec.php + test_concat.php + test_dynamic.php + test_max_water_prefs.php + test_min_water_prefs.php + test_normal_water_prefs.php + test_prefs.php + test_projects.php + test_stderr.php + test_uc.php + test_uc_slow.php + uc_result + ucs_result + tools/ + Makefile.in + add.C + backend_lib.C,h + create_work.C + process_result_template.C diff --git a/client/Makefile.in b/client/Makefile.in index c4b99955a5..dbcf4874be 100644 --- a/client/Makefile.in +++ b/client/Makefile.in @@ -56,6 +56,8 @@ TEST_HTTP_OBJS = $(TEST_NET_XFER_OBJS) TEST_FX_OBJS = \ $(TEST_HTTP_OBJS) \ + client_types.o \ + file_names.o \ file_xfer.o .C.o: diff --git a/client/client_state.C b/client/client_state.C index 416978de5f..9df6190cb5 100644 --- a/client/client_state.C +++ b/client/client_state.C @@ -195,7 +195,7 @@ int CLIENT_STATE::parse_state_file() { } } else if (match_tag(buf, "")) { FILE_INFO* fip = new FILE_INFO; - fip->parse(f); + fip->parse(f, false); if (project) { retval = link_file_info(project, fip); if (!retval) file_infos.push_back(fip); @@ -222,7 +222,7 @@ int CLIENT_STATE::parse_state_file() { } } else if (match_tag(buf, "")) { RESULT* rp = new RESULT; - rp->parse(f, ""); + rp->parse_state(f); if (project) { retval = link_result(project, rp); if (!retval) results.push_back(rp); diff --git a/client/client_types.C b/client/client_types.C index b4ab885ef3..00cedfa66b 100644 --- a/client/client_types.C +++ b/client/client_types.C @@ -174,14 +174,17 @@ FILE_INFO::FILE_INFO() { FILE_INFO::~FILE_INFO() { } -int FILE_INFO::parse(FILE* in) { +// If from server, make an exact copy of everything +// except the start/end tags and the signature element. +// +int FILE_INFO::parse(FILE* in, bool from_server) { char buf[256]; STRING256 url; strcpy(name, ""); - //strcpy(url, ""); strcpy(md5_cksum, ""); nbytes = 0; + max_nbytes = 0; generated_locally = false; file_present = false; executable = false; @@ -191,21 +194,39 @@ int FILE_INFO::parse(FILE* in) { project = NULL; file_xfer = NULL; urls.clear(); + if (from_server) { + signed_xml = strdup(""); + } else { + signed_xml = 0; + } + signature = 0; while (fgets(buf, 256, in)) { if (match_tag(buf, "")) return 0; - else if (parse_str(buf, "", name)) continue; + else if (match_tag(buf, "")) { + dup_element_contents(in, "", &signature); + continue; + } + if (from_server) { + strcatdup(signed_xml, buf); + } + if (parse_str(buf, "", name)) continue; else if (parse_str(buf, "", url.text)) { urls.push_back(url); continue; } else if (parse_str(buf, "", md5_cksum)) continue; else if (parse_double(buf, "", nbytes)) continue; + else if (parse_double(buf, "", max_nbytes)) continue; else if (match_tag(buf, "")) generated_locally = true; else if (match_tag(buf, "")) file_present = true; else if (match_tag(buf, "")) executable = true; else if (match_tag(buf, "")) uploaded = true; else if (match_tag(buf, "")) upload_when_present = true; else if (match_tag(buf, "")) sticky = true; + else if (!from_server && match_tag(buf, "")) { + dup_element_contents(in, "", &signed_xml); + continue; + } else fprintf(stderr, "FILE_INFO::parse(): unrecognized: %s\n", buf); } return 1; @@ -217,8 +238,9 @@ int FILE_INFO::write(FILE* out, bool to_server) { "\n" " %s\n" " %s\n" - " %f\n", - name, md5_cksum, nbytes + " %f\n" + " %f\n", + name, md5_cksum, nbytes, max_nbytes ); if (!to_server) { if (generated_locally) fprintf(out, " \n"); @@ -231,6 +253,14 @@ int FILE_INFO::write(FILE* out, bool to_server) { for (i=0; i%s\n", urls[i].text); } + if (!to_server) { + if (signed_xml) { + fprintf(out, "\n%s\n", signed_xml); + } + if (signature) { + fprintf(out, "\n%s\n", signature); + } + } fprintf(out, "\n"); return 0; } @@ -369,26 +399,63 @@ int WORKUNIT::write(FILE* out) { return 0; } -int RESULT::parse(FILE* in, char* end_tag) { +int RESULT::parse_ack(FILE* in) { char buf[256]; - FILE_REF file_ref; + strcpy(name, ""); + while (fgets(buf, 256, in)) { + if (match_tag(buf, "")) return 0; + else if (parse_str(buf, "", name)) continue; + else fprintf(stderr, "RESULT::parse(): unrecognized: %s\n", buf); + } + return 1; +} +void RESULT::clear() { strcpy(name, ""); strcpy(wu_name, ""); - strcpy(stderr_out, ""); + output_files.clear(); is_active = false; is_compute_done = false; is_server_ack = false; cpu_time = 0; exit_status = 0; + strcpy(stderr_out, ""); app = NULL; wup = NULL; project = NULL; +} + +// parse a element from scheduling server. +// +int RESULT::parse_server(FILE* in) { + char buf[256]; + FILE_REF file_ref; + while (fgets(buf, 256, in)) { - if (match_tag(buf, end_tag)) return 0; - else if (parse_str(buf, "", name)) continue; - else if (parse_str(buf, "", wu_name)) continue; - else if (match_tag(buf, "")) { + if (match_tag(buf, "")) return 0; + if (parse_str(buf, "", name)) continue; + if (parse_str(buf, "", wu_name)) continue; + if (match_tag(buf, "")) { + file_ref.parse(in); + output_files.push_back(file_ref); + continue; + } + else fprintf(stderr, "RESULT::parse(): unrecognized: %s\n", buf); + } + return 1; +} + +// parse a element from state file +// +int RESULT::parse_state(FILE* in) { + char buf[256]; + FILE_REF file_ref; + + while (fgets(buf, 256, in)) { + if (match_tag(buf, "")) return 0; + if (parse_str(buf, "", name)) continue; + if (parse_str(buf, "", wu_name)) continue; + if (match_tag(buf, "")) { file_ref.parse(in); output_files.push_back(file_ref); continue; diff --git a/client/client_types.h b/client/client_types.h index c73405c959..07a4a422f0 100644 --- a/client/client_types.h +++ b/client/client_types.h @@ -35,6 +35,7 @@ #define STDERR_MAX_LEN 4096 class FILE_XFER; +class RESULT; struct STRING256 { char text[256]; @@ -85,8 +86,8 @@ struct APP { class FILE_INFO { public: char name[256]; - //char url[256]; // TODO: allow multiple URLs char md5_cksum[33]; + double max_nbytes; double nbytes; bool generated_locally; // file is produced by app bool file_present; @@ -95,13 +96,16 @@ public: bool upload_when_present; bool sticky; // don't delete unless instructed to do so FILE_XFER* file_xfer; // nonzero if in the process of being up/downloaded + RESULT* result; // for upload files (to authenticate) PROJECT* project; int ref_cnt; vector urls; + char* signed_xml; + char* signature; FILE_INFO(); ~FILE_INFO(); - int parse(FILE*); + int parse(FILE*, bool from_server); int write(FILE*, bool to_server); int delete_file(); // attempt to delete the underlying file }; @@ -168,7 +172,10 @@ struct RESULT { WORKUNIT* wup; PROJECT* project; - int parse(FILE*, char* end_tag); + void clear(); + int parse_server(FILE*); + int parse_state(FILE*); + int parse_ack(FILE*); int write(FILE*, bool to_server); bool is_upload_done(); // files uploaded? }; diff --git a/client/cs_files.C b/client/cs_files.C index 54dbf833cc..4e964a6521 100644 --- a/client/cs_files.C +++ b/client/cs_files.C @@ -54,8 +54,8 @@ bool CLIENT_STATE::start_file_xfers() { fxp = fip->file_xfer; if (!fip->generated_locally && !fip->file_present && !fxp) { fxp = new FILE_XFER; - get_pathname(fip, pathname); - fxp->init_download(fip->urls[0].text, pathname); + //get_pathname(fip, pathname); + fxp->init_download(*fip); retval = file_xfers->insert(fxp); if (retval) { fprintf(stderr, @@ -66,8 +66,8 @@ bool CLIENT_STATE::start_file_xfers() { fip->file_xfer = fxp; if (log_flags.file_xfer) { printf( - "started download of %s to %s\n", - fip->urls[0].text, pathname + "started download of %s\n", + fip->urls[0].text ); } } @@ -77,8 +77,7 @@ bool CLIENT_STATE::start_file_xfers() { && !fip->uploaded && !fxp ) { fxp = new FILE_XFER; - get_pathname(fip, pathname); - fxp->init_upload( fip->urls[0].text, pathname); + fxp->init_upload(*fip); retval = file_xfers->insert(fxp); if (retval) { fprintf(stderr, @@ -88,10 +87,7 @@ bool CLIENT_STATE::start_file_xfers() { } else { fip->file_xfer = fxp; if (log_flags.file_xfer) { - printf( - "started upload of %s to %s\n", - pathname, fip->urls[0].text - ); + printf("started upload to %s\n", fip->urls[0].text); } } action = true; diff --git a/client/file_names.C b/client/file_names.C index a9c521a487..793939c4b2 100644 --- a/client/file_names.C +++ b/client/file_names.C @@ -71,8 +71,15 @@ void get_pathname(FILE_INFO* fip, char* path) { PROJECT* p = fip->project; char buf[256]; - escape_url(p->master_url, buf); - sprintf(path, "%s/%s", buf, fip->name); + // for testing purposes, it's handy to allow a FILE_INFO without + // an associated PROJECT. + // + if (p) { + escape_url(p->master_url, buf); + sprintf(path, "%s/%s", buf, fip->name); + } else { + strcpy(path, fip->name); + } } void get_slot_dir(int slot, char* path) { diff --git a/client/file_xfer.C b/client/file_xfer.C index f9f7819e1c..ff49718f2e 100644 --- a/client/file_xfer.C +++ b/client/file_xfer.C @@ -20,6 +20,7 @@ #include "windows_cpp.h" #include "util.h" +#include "file_names.h" #include "log_flags.h" #include "file_xfer.h" @@ -31,6 +32,7 @@ FILE_XFER::FILE_XFER() { FILE_XFER::~FILE_XFER() { } +#if 0 int FILE_XFER::init_download(char* url, char* outfile) { return HTTP_OP::init_get(url, outfile); } @@ -38,6 +40,37 @@ int FILE_XFER::init_download(char* url, char* outfile) { int FILE_XFER::init_upload(char* url, char* infile) { return HTTP_OP::init_put(url, infile); } +#endif + +int FILE_XFER::init_download(FILE_INFO& file_info) { + fip = &file_info; + get_pathname(fip, pathname); + return HTTP_OP::init_get((char*)(&fip->urls[0]), pathname); +} + +// for uploads, we need to build a header with signature etc. +// (see file_upload_handler.C for a spec) +// Do this in memory. +// +int FILE_XFER::init_upload(FILE_INFO& file_info) { + fip = &file_info; + get_pathname(fip, pathname); + sprintf(header, + "\n" + "%s" + "\n" + "%s" + "\n" + "\n" + "%f\n" + "0\n" + "\n", + file_info.signed_xml, + file_info.signature, + file_info.nbytes + ); + return HTTP_OP::init_post2((char*)(&fip->urls[0]), header, pathname, 0); +} double FILE_XFER::elapsed_time() { return end_time - start_time; diff --git a/client/file_xfer.h b/client/file_xfer.h index e6d9e4fbbc..5dbfe213cd 100644 --- a/client/file_xfer.h +++ b/client/file_xfer.h @@ -20,9 +20,14 @@ #ifndef _FILE_XFER_ #define _FILE_XFER_ -// FILE_XFER objects encapsulate the transfer of a file -// to/from a particular server. -// TODO: use the HTTP Range header fields to do partial xfers +// FILE_XFER objects encapsulate the transfer of a file to/from data servers. +// In particular it manages: +// - the choice of data servers +// TODO: try servers beyond the first one +// - the retry and give-up policies +// TODO: retry and eventually give up +// - restarting partial transfers +// - upload authentication #include "client_types.h" #include "http.h" @@ -32,12 +37,17 @@ public: double start_time; double end_time; FILE_INFO* fip; + char pathname[256]; + char header[4096]; + int state; FILE_XFER(); ~FILE_XFER(); - int init_download(char* url, char* outfile); - int init_upload(char* url, char* infile); + //int init_download(char* url, char* outfile); + //int init_upload(char* url, char* infile); + int init_download(FILE_INFO&); + int init_upload(FILE_INFO&); bool file_xfer_done; int file_xfer_retval; double elapsed_time(); diff --git a/client/http.C b/client/http.C index e1b067e6e5..aa540bd95b 100644 --- a/client/http.C +++ b/client/http.C @@ -21,6 +21,7 @@ #include #include +#include #ifdef _WIN32 #include "winsock.h" @@ -35,7 +36,7 @@ #define HTTP_BLOCKSIZE 4096 -void parse_url(char* url, char* host, char* file) { +static void parse_url(char* url, char* host, char* file) { char* p; char buf[256]; @@ -55,7 +56,9 @@ void parse_url(char* url, char* host, char* file) { // We use 1.0 so we don't have to count bytes. // -void http_get_request_header(char* buf, char* host, char* file, int offset) { +static void http_get_request_header( + char* buf, char* host, char* file, int offset +) { if (offset) { sprintf(buf, "GET /%s;byte-range %d- HTTP/1.0\015\012" @@ -79,7 +82,7 @@ void http_get_request_header(char* buf, char* host, char* file, int offset) { } } -void http_head_request_header(char* buf, char* host, char* file) { +static void http_head_request_header(char* buf, char* host, char* file) { sprintf(buf, "HEAD /%s HTTP/1.0\015\012" "User-Agent: BOINC client\015\012" @@ -90,7 +93,9 @@ void http_head_request_header(char* buf, char* host, char* file) { ); } -void http_post_request_header(char* buf, char* host, char* file, int size) { +static void http_post_request_header( + char* buf, char* host, char* file, int size +) { sprintf(buf, "POST /%s HTTP/1.0\015\012" "Pragma: no-cache\015\012" @@ -103,6 +108,7 @@ void http_post_request_header(char* buf, char* host, char* file, int size) { ); } +#if 0 void http_put_request_header( char* buf, char* host, char* file, int size, int offset ) { @@ -132,20 +138,17 @@ void http_put_request_header( ); } } +#endif int read_http_reply_header(int socket, HTTP_REPLY_HEADER& header) { - int i; + int i, n; char buf[1024], *p; memset(buf, 0, sizeof(buf)); header.content_length = 0; header.status = 404; // default to failure for (i=0; i<1024; i++) { -#ifdef _WIN32 - recv(socket, buf+i, 1, 0); -#else - read(socket, buf+i, 1); -#endif + n = recv(socket, buf+i, 1, 0); if (strstr(buf, "\r\n\r\n") || strstr(buf, "\n\n")) { if (log_flags.http_debug) printf("reply header:\n%s", buf); p = strchr(buf, ' '); @@ -162,6 +165,16 @@ int read_http_reply_header(int socket, HTTP_REPLY_HEADER& header) { return 1; } +static int read_reply(int socket, char* buf, int len) { + int i, n; + for (i=0; iio_ready) { action = true; - switch(htp->http_op_type) { - case HTTP_OP_POST: - http_post_request_header( - hdr, htp->hostname, htp->filename, htp->content_length - ); - break; - case HTTP_OP_GET: - http_get_request_header(hdr, htp->hostname, htp->filename, htp->offset); - break; - case HTTP_OP_HEAD: - http_head_request_header(hdr, htp->hostname, htp->filename); - break; - case HTTP_OP_PUT: - http_put_request_header( - hdr, htp->hostname, htp->filename, htp->content_length, htp->offset - ); - break; - } -#ifdef _WIN32 - n = send(htp->socket, hdr, strlen(hdr), 0); -#else - n = write(htp->socket, hdr, strlen(hdr)); -#endif + n = send(htp->socket, htp->request_header, strlen(htp->request_header), 0); if (log_flags.http_debug) { printf("wrote HTTP header: %d bytes\n", n); } htp->io_ready = false; switch(htp->http_op_type) { case HTTP_OP_POST: - case HTTP_OP_PUT: + //case HTTP_OP_PUT: htp->http_op_state = HTTP_STATE_REQUEST_BODY; htp->file = fopen(htp->infile, "r"); if (!htp->file) { @@ -298,9 +320,30 @@ bool HTTP_OP_SET::poll() { htp->want_upload = false; htp->want_download = true; break; + case HTTP_OP_POST2: + htp->http_op_state = HTTP_STATE_REQUEST_BODY1; + break; } } break; + case HTTP_STATE_REQUEST_BODY1: + if (htp->io_ready) { + action = true; + n = send(htp->socket, htp->req1, strlen(htp->req1), 0); + htp->http_op_state = HTTP_STATE_REQUEST_BODY; + htp->file = fopen(htp->infile, "r"); + if (!htp->file) { + fprintf(stderr, "HTTP_OP: no input file %s\n", htp->infile); + htp->io_done = true; + htp->http_op_retval = ERR_FOPEN; + htp->http_op_state = HTTP_STATE_DONE; + break; + } + fseek(htp->file, (long)htp->file_offset, SEEK_SET); + htp->io_ready = false; + htp->do_file_io = true; + } + break; case HTTP_STATE_REQUEST_BODY: if (htp->io_done) { action = true; @@ -313,6 +356,7 @@ bool HTTP_OP_SET::poll() { htp->do_file_io = false; htp->want_upload = false; htp->want_download = true; + htp->io_ready = false; } case HTTP_STATE_REPLY_HEADER: if (htp->io_ready) { @@ -349,22 +393,35 @@ bool HTTP_OP_SET::poll() { } htp->do_file_io = true; break; + case HTTP_OP_POST2: + htp->http_op_state = HTTP_STATE_REPLY_BODY; + htp->io_ready = false; + break; +#if 0 case HTTP_OP_PUT: htp->http_op_state = HTTP_STATE_DONE; htp->http_op_retval = 0; +#endif } } break; case HTTP_STATE_REPLY_BODY: if (htp->io_done) { - action = true; + switch(htp->http_op_type) { + case HTTP_OP_POST2: + read_reply(htp->socket, htp->req1, 256); + break; + default: + action = true; + fclose(htp->file); + htp->file = 0; + break; + } if (log_flags.http_debug) printf("got reply body\n"); - fclose(htp->file); - htp->file = 0; htp->http_op_state = HTTP_STATE_DONE; htp->http_op_retval = 0; - break; } + break; } } return action; diff --git a/client/http.h b/client/http.h index b19b7a4bef..30acf54f38 100644 --- a/client/http.h +++ b/client/http.h @@ -17,6 +17,10 @@ // Contributor(s): // +// HTTP_OP represents an HTTP operation. +// There are variants for GET, POST etc. +// as well as for the data source/sink. + #ifndef _HTTP_ #define _HTTP_ @@ -27,13 +31,16 @@ struct HTTP_REPLY_HEADER { int content_length; }; +// For the first 4, data source/sink are files #define HTTP_OP_GET 1 #define HTTP_OP_POST 2 -#define HTTP_OP_PUT 3 +//#define HTTP_OP_PUT 3 #define HTTP_OP_HEAD 4 +#define HTTP_OP_POST2 5 + // a POST operation where the request comes from a combination + // of a string and a file w/offset, + // and the reply goes into a memory buffer -// represents an HTTP request in progress -// class HTTP_OP : public NET_XFER { public: HTTP_OP(); @@ -41,10 +48,12 @@ public: char hostname[256]; char filename[256]; + char* req1; char infile[256]; char outfile[256]; int content_length; - int offset; + double file_offset; + char request_header[256]; HTTP_REPLY_HEADER hrh; int http_op_state; // values below int http_op_type; @@ -53,7 +62,10 @@ public: int init_head(char* url); int init_get(char* url, char* outfile, int offset=0); int init_post(char* url, char* infile, char* outfile); - int init_put(char* url, char* infile, int offset=0); + int init_post2( + char* url, char* req1, char* infile, double offset + ); + //int init_put(char* url, char* infile, int offset=0); bool http_op_done(); }; @@ -73,10 +85,11 @@ public: #define HTTP_STATE_IDLE 0 #define HTTP_STATE_CONNECTING 1 #define HTTP_STATE_REQUEST_HEADER 2 -#define HTTP_STATE_REQUEST_BODY 3 -#define HTTP_STATE_REPLY_HEADER 4 -#define HTTP_STATE_REPLY_BODY 5 -#define HTTP_STATE_DONE 6 +#define HTTP_STATE_REQUEST_BODY1 3 +#define HTTP_STATE_REQUEST_BODY 4 +#define HTTP_STATE_REPLY_HEADER 5 +#define HTTP_STATE_REPLY_BODY 6 +#define HTTP_STATE_DONE 7 extern int read_http_reply_header(int socket, HTTP_REPLY_HEADER&); diff --git a/client/main.C b/client/main.C index 26419a21c6..9444e3a4af 100644 --- a/client/main.C +++ b/client/main.C @@ -104,6 +104,7 @@ int main(int argc, char** argv) { boinc_sleep(1); } if (cs.time_to_exit()) { + printf("time to exit\n"); break; } } diff --git a/client/scheduler_op.C b/client/scheduler_op.C index c4fd1a06ae..87b40a4c18 100644 --- a/client/scheduler_op.C +++ b/client/scheduler_op.C @@ -162,7 +162,7 @@ int SCHEDULER_REPLY::parse(FILE* in) { apps.push_back(app); } else if (match_tag(buf, "")) { FILE_INFO file_info; - file_info.parse(in); + file_info.parse(in, true); file_infos.push_back(file_info); } else if (match_tag(buf, "")) { APP_VERSION av; @@ -175,11 +175,11 @@ int SCHEDULER_REPLY::parse(FILE* in) { } else if (match_tag(buf, "")) { RESULT result; // make sure this is here so constructor // gets called each time - result.parse(in, ""); + result.parse_server(in); results.push_back(result); } else if (match_tag(buf, "")) { RESULT result; - result.parse(in, ""); + result.parse_ack(in); result_acks.push_back(result); } else if (parse_str(buf, "init_download(DOWNLOAD_URL, "test_fx_out"); - if (retval) { - printf("init_download failed\n"); - exit(1); - } - FILE_XFER* fx2= new FILE_XFER; - retval = fx2->init_upload(UPLOAD_URL, "test_fx_in"); - if (retval) { - printf("init_upload failed\n"); - exit(1); + if (do_download) { + fx1 = new FILE_XFER; + memset(&fi1, 0, sizeof(fi1)); + strcpy(fi1.name, "test_fx_out"); + strcpy(str.text, DOWNLOAD_URL); + fi1.urls.push_back(str); + retval = fx1->init_download(fi1); + if (retval) { + printf("init_download failed\n"); + exit(1); + } + retval = fxs.insert(fx1); + if (retval) { + printf("insert failed\n"); + exit(1); + } } - retval = fxs.insert(fx1); - if (retval) { - printf("insert failed\n"); - exit(1); - } - retval = fxs.insert(fx2); - if (retval) { - printf("insert failed\n"); - exit(1); + if (do_upload) { + fx2= new FILE_XFER; + memset(&fi2, 0, sizeof(fi1)); + strcpy(fi2.name, "test_fx_in"); + strcpy(str.text, UPLOAD_URL); + fi2.urls.push_back(str); + fi2.signed_xml = \ + " uc_wu_1_0\n" + " \n" + " \n" + " 10000\n" + " http://localhost/upload/uc_wu_1_0\n"; + fi2.signature = \ + "9d1f8152371c67af1d26b25db104014dbf7e9ad3b61fc8334ee06e01c7529b1a\n" + "7681c3e7c7828525361a01040d1197147286085231ee5d2554e59ecb40b3e6a5\n" + "afbaf00ff15bc5b1acf5aa6318bc84f2671a9502ada9c2ce37a9c45480a0e3b7\n" + "b3dcb6c3bf09feaebc81b76063ef12b0031cf041eaef811166839533067b74f6\n" + ".\n"; + retval = fx2->init_upload(fi2); + if (retval) { + printf("init_upload failed\n"); + exit(1); + } + retval = fxs.insert(fx2); + if (retval) { + printf("insert failed\n"); + exit(1); + } } + while (1) { nxs.poll(100000, n); hos.poll(); diff --git a/db/mysql_util.C b/db/mysql_util.C index 311965385d..f543ea6bea 100644 --- a/db/mysql_util.C +++ b/db/mysql_util.C @@ -36,7 +36,7 @@ static MYSQL *mp; static MYSQL_RES *rp; static MYSQL_ROW row; -#define MAX_QUERY_LEN 4096 +#define MAX_QUERY_LEN 8192 int db_open(char* name) { mp = mysql_init(0); diff --git a/doc/index.html b/doc/index.html index 563a43b247..3c28578f9f 100644 --- a/doc/index.html +++ b/doc/index.html @@ -19,16 +19,18 @@
  • Back end examples
  • Versioning
  • The BOINC application library -
  • Graphics +
  • Client graphics
  • Application development

    Operating a BOINC project

    • Installing BOINC -
    • Project startup +
    • The master URL +
    • Project startup checklist
    • The BOINC database
    • The BOINC scheduling server +
    • Operational tools: security
    • Operational tools: applications and versions
    • Operational tools: work and results
    • The project web site diff --git a/doc/intro.html b/doc/intro.html index d1d3e0c5be..fa2ade069a 100644 --- a/doc/intro.html +++ b/doc/intro.html @@ -12,7 +12,7 @@ The features of BOINC include:
      • BOINC allows multiple independent projects -to share a common set of participants. +to share participants. Participants download a single core client program, which in turn downloads and executes project-specific executables. Participants can control how their resources are divided among the projects. @@ -28,14 +28,18 @@ Work is dispatched only to hosts able to handle it.
      • BOINC applications can be developed in any language (C++, Fortran, Perl). An application can consist of several files -(e.g. several programs and a coordinating script). -New versions of applications can be released without participant download. +(e.g. multiple programs and a coordinating script). +New versions of applications can be deployed without participant download. Separate alpha, beta, and production versions are distributed to the appropriate set of hosts.
      • The BOINC core client can run on almost any platform (Mac, Windows, Linux and other Unix systems). +
      • A BOINC project must provide and maintain its own server systems, +but these systems can be set up easily and involve +only open-source components (MySQL, PHP, Apache, and Linux). +
      • BOINC is distributed under the Mozilla license.
      diff --git a/doc/master_url.html b/doc/master_url.html new file mode 100644 index 0000000000..1a58308e31 --- /dev/null +++ b/doc/master_url.html @@ -0,0 +1,27 @@ +

      The master URL

      + +

      +Each project is identified by a master URL. +This URL is used to publicize the project. +The master page at this URL has two functions. +

        +
      • +It is the home page of the project; +when viewed in a browser it describes the project +and contains links for registering +and for downloading the core client. +
      • +It contains XML tags of the form +
        +<scheduler>http:host.domain.edu/cgi/scheduler<scheduler>
        +<scheduler>http:host2.domain.edu/cgi/scheduler<scheduler>
        +
        +that give the URLs of the project's scheduling servers. +These tags can be embedded within HTML comments. +The BOINC core client reads and parses the master page +to find scheduler servers. +If it is unable to connect to any scheduler server for a project, +it rereads the master page. +
      +This mechanism lets a project change the locations of its scheduling servers, +or add new servers. diff --git a/doc/tools_security.html b/doc/tools_security.html new file mode 100644 index 0000000000..ec3dd59688 --- /dev/null +++ b/doc/tools_security.html @@ -0,0 +1,25 @@ +

      Security tools

      + +

      +The program lib/crypt_prog can be used for several purposes: + +
      +
      +-genkey n private_keyfile public_keyfile +
      +Create a key pair with n bits (always use 1024). +Write the keys in encoded ASCII form to the indicated files. +
      +-sign file private_keyfile +
      +Create a digital signature for the given file. +Write it in encoded ASCII to stdout. +
      +-verify file signature_file public_keyfile +
      +Verify a signature for the given file. +
      +-test_crypt private_keyfile public_keyfile +
      +Perform an internal test, checking that encryption +followed by decryption works. diff --git a/html/user/index.html b/html/user/index.html index 278e738d1f..0c83f5beac 100644 --- a/html/user/index.html +++ b/html/user/index.html @@ -1,3 +1,7 @@ +

      Test BOINC Project

      + +http://localhost/boinc-cgi/cgi +
      Log in or create account
      User page
      Download core client diff --git a/lib/Makefile.in b/lib/Makefile.in index 9a1db0c0a0..91559c04a7 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -11,6 +11,10 @@ CC = @CC@ $(CFLAGS) -I ../RSAEuro/source PROGS = md5_test shmem_test synch_test crypt_prog +OBJS = \ + countries.o \ + parse.o + MD5_OBJS = \ md5.o \ md5_file.o @@ -25,7 +29,7 @@ CRYPT_LIBS = \ COUNTRY_OBJS = \ countries.o -all: $(PROGS) $(MD5_OBJS) $(CRYPT_OBJS) +all: $(PROGS) $(OBJS) $(MD5_OBJS) $(CRYPT_OBJS) .C.o: $(CC) -c -o $*.o $< diff --git a/lib/crypt.C b/lib/crypt.C index 0d6b536ef1..5e5af97879 100644 --- a/lib/crypt.C +++ b/lib/crypt.C @@ -1,14 +1,10 @@ #include #include -#include "rsaeuro.h" -extern "C" { -#include "rsa.h" -} #include "md5_file.h" #include "crypt.h" -// print some data in hex notation. +// write some data in hex notation. // NOTE: since length may not be known to the reader, // we follow the data with a non-hex character '.' // @@ -23,6 +19,22 @@ int print_hex_data(FILE* f, DATA_BLOCK& x) { fprintf(f, ".\n"); } +// same, but write to buffer +// +int sprint_hex_data(char* p, DATA_BLOCK& x) { + int i; + char buf[16]; + + strcpy(p, ""); + for (i=0; ibits); len = size - sizeof(key->bits); for (i=0; idata+i); + fscanf(f, "%2x", &n); + key->data[i] = n; } fscanf(f, "."); return 0; @@ -72,14 +100,15 @@ int encrypt_private( R_RSA_PRIVATE_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out, int& nbytes_encrypted ) { - int retval, n; + int retval, n, modulus_len; + modulus_len = (key.bits+7)/8; n = in.len; - if (n >= key.bits-11) { - n = key.bits-11; + if (n >= modulus_len-11) { + n = modulus_len-11; } retval = RSAPrivateEncrypt(out.data, &out.len, in.data, n, &key); - if (retval) return retval; - nbytes_encrypted = n; + if (retval ) return retval; + nbytes_encrypted = retval; return 0; } @@ -88,7 +117,7 @@ int decrypt_public(R_RSA_PUBLIC_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out) { } int sign_file(char* path, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { - char md5_buf[64]; + char md5_buf[MD5_LEN]; double file_length; DATA_BLOCK in_block; int retval, n; @@ -102,10 +131,26 @@ int sign_file(char* path, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { return 0; } +int sign_block(DATA_BLOCK& data_block, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { + char md5_buf[MD5_LEN]; + int retval, n; + DATA_BLOCK in_block; + + md5_block(data_block.data, data_block.len, md5_buf); + in_block.data = (unsigned char*)md5_buf; + in_block.len = strlen(md5_buf); + retval = encrypt_private(key, in_block, signature, n); + if (retval) { + printf("sign_block: encrypt_private returned %d\n", retval); + return retval; + } + return 0; +} + int verify_file( char* path, R_RSA_PUBLIC_KEY& key, DATA_BLOCK& signature, bool& answer ) { - char md5_buf[64], clear_buf[256]; + char md5_buf[MD5_LEN], clear_buf[256]; double file_length; int n, retval; DATA_BLOCK clear_signature; @@ -120,3 +165,27 @@ int verify_file( answer = !strncmp(md5_buf, clear_buf, n); return 0; } + +// verify, where both text and signature are char strings +// +int verify_string( + char* text, char* signature_text, R_RSA_PUBLIC_KEY& key, bool& answer +) { + char md5_buf[MD5_LEN]; + unsigned char signature_buf[SIGNATURE_SIZE]; + char clear_buf[MD5_LEN]; + int retval, n; + DATA_BLOCK signature, clear_signature; + + retval = md5_block((unsigned char*)text, strlen(text), md5_buf); + if (retval) return retval; + n = strlen(md5_buf); + signature.data = signature_buf; + sscan_hex_data(signature_text, signature); + clear_signature.data = (unsigned char*)clear_buf; + clear_signature.len = 256; + retval = decrypt_public(key, signature, clear_signature); + if (retval) return retval; + answer = !strncmp(md5_buf, clear_buf, n); + return 0; +} diff --git a/lib/crypt.h b/lib/crypt.h index d935fc6543..cecfa758eb 100644 --- a/lib/crypt.h +++ b/lib/crypt.h @@ -1,5 +1,12 @@ +#ifndef _CRYPT_ +#define _CRYPT_ // some interface functions for RSAEuro +#include "rsaeuro.h" +extern "C" { +#include "rsa.h" +} + struct KEY { unsigned short int bits; unsigned char data[1]; @@ -12,8 +19,14 @@ struct DATA_BLOCK { #define MIN_OUT_BUFFER_SIZE MAX_RSA_MODULUS_LEN+1 +// the size of a signature (encrypted MD5) +// +#define SIGNATURE_SIZE MIN_OUT_BUFFER_SIZE + int print_hex_data(FILE* f, DATA_BLOCK&); +int sprint_hex_data(char* p, DATA_BLOCK&); int scan_hex_data(FILE* f, DATA_BLOCK&); +int sscan_hex_data(char* p, DATA_BLOCK&); int print_key_hex(FILE*, KEY* key, int len); int scan_key_hex(FILE*, KEY* key, int len); int encrypt_private( @@ -21,4 +34,8 @@ int encrypt_private( ); int decrypt_public(R_RSA_PUBLIC_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out); int sign_file(char* path, R_RSA_PRIVATE_KEY&, DATA_BLOCK& signature); +int sign_block(DATA_BLOCK& data, R_RSA_PRIVATE_KEY&, DATA_BLOCK& signature); int verify_file(char* path, R_RSA_PUBLIC_KEY&, DATA_BLOCK& signature, bool&); +int verify_string(char* text, char* signature, R_RSA_PUBLIC_KEY&, bool&); + +#endif diff --git a/lib/crypt_prog.C b/lib/crypt_prog.C index b2fe4e22c2..77d0716857 100644 --- a/lib/crypt_prog.C +++ b/lib/crypt_prog.C @@ -1,14 +1,14 @@ // utility program for encryption. // // -genkey n private_keyfile public_keyfile -// create a key pair with n bits (512 <= n <= 2048) +// create a key pair with n bits (512 <= n <= 1024) // write it in hex notation // -sign file private_keyfile // create a signature for a given file // write it in hex notation // -verify file signature_file public_keyfile // verify a signature -// -crypt_test private_keyfile public_keyfile +// -test_crypt private_keyfile public_keyfile // test encrypt/decrypt #include @@ -50,6 +50,7 @@ main(int argc, char** argv) { ); if (retval) die("R_GeneratePEMKeys\n"); + printf("creating keys in %s and %s\n", argv[3], argv[4]); fpriv = fopen(argv[3], "w"); if (!fpriv) die("fopen"); fpub = fopen(argv[4], "w"); diff --git a/lib/md5_file.C b/lib/md5_file.C index 7d88f16718..2a01773552 100644 --- a/lib/md5_file.C +++ b/lib/md5_file.C @@ -32,3 +32,18 @@ int md5_file(char* path, char* output, double& nbytes) { fclose(f); return 0; } + +int md5_block(unsigned char* data, int nbytes, char* output) { + unsigned char binout[16]; + int i; + + md5_state_t state; + md5_init(&state); + md5_append(&state, data, nbytes); + md5_finish(&state, binout); + for (i=0; i<16; i++) { + sprintf(output+2*i, "%02x", binout[i]); + } + output[32] = 0; + return 0; +} diff --git a/lib/md5_file.h b/lib/md5_file.h index 33396a1d65..6b2d94c6d7 100644 --- a/lib/md5_file.h +++ b/lib/md5_file.h @@ -1 +1,5 @@ +// length of buffer to hold an MD5 hash +#define MD5_LEN 64 + extern int md5_file(char* path, char* output, double& nbytes); +extern int md5_block(unsigned char* data, int nbytes, char* output); diff --git a/lib/parse.C b/lib/parse.C index 29282a9543..aa4abe6be3 100644 --- a/lib/parse.C +++ b/lib/parse.C @@ -87,18 +87,25 @@ void copy_stream(FILE* in, FILE* out) { } } +void strcatdup(char*& p, char* buf) { + p = (char*)realloc(p, strlen(p) + strlen(buf)+1); + if (!p) { + fprintf(stderr, "strcatdup: realloc failed\n"); + exit(1); + } + strcat(p, buf); +} + int dup_element_contents(FILE* in, char* end_tag, char** pp) { char buf[256]; - char* p = (char*)malloc(1); - *p = 0; + char* p = strdup(""); while (fgets(buf, 256, in)) { if (strstr(buf, end_tag)) { *pp = p; return 0; } - p = (char*)realloc(p, strlen(p) + strlen(buf)+1); - strcat(p, buf); + strcatdup(p, buf); } fprintf(stderr, "dup_element_contents(): no end tag\n"); return 1; diff --git a/lib/parse.h b/lib/parse.h index 469e7e9bbb..d27011f47e 100644 --- a/lib/parse.h +++ b/lib/parse.h @@ -26,4 +26,5 @@ extern bool parse_str(char*, char*, char*); extern void parse_attr(char* buf, char* attrname, char* out); extern bool match_tag(char*, char*); extern void copy_stream(FILE* in, FILE* out); +extern void strcatdup(char*& p, char* buf); extern int dup_element_contents(FILE* in, char* end_tag, char** pp); diff --git a/sched/Makefile.in b/sched/Makefile.in index 59aaba6898..54c07907c8 100644 --- a/sched/Makefile.in +++ b/sched/Makefile.in @@ -5,12 +5,18 @@ VPATH = @srcdir@ all: cgi -CFLAGS = -g -Wall @DEFS@ -I@top_srcdir@/db -I@top_srcdir@/lib -I@top_srcdir@/tools -I/usr/local/mysql/include +CFLAGS = -g -Wall @DEFS@ \ + -I@top_srcdir@/db \ + -I@top_srcdir@/lib \ + -I@top_srcdir@/RSAEuro/source \ + -I@top_srcdir@/tools \ + -I/usr/local/mysql/include + CC = g++ $(CFLAGS) CLIBS = @LIBS@ -PROGS = cgi feeder show_shmem fcgi +PROGS = cgi feeder show_shmem file_upload_handler fcgi all: $(PROGS) @@ -22,8 +28,7 @@ CGI_OBJS = \ ../db/db_mysql.o \ ../db/mysql_util.o \ ../lib/shmem.o \ - ../lib/parse.o \ - ../tools/process_result_template.o + ../lib/parse.o FEEDER_OBJS = \ feeder.o \ @@ -39,6 +44,14 @@ SHOW_SHMEM_OBJS = \ ../db/mysql_util.o \ ../lib/shmem.o +FILE_UPLOAD_OBJS = \ + file_upload_handler.o \ + ../lib/crypt.o \ + ../lib/parse.o \ + ../lib/md5.o \ + ../lib/md5_file.o \ + ../RSAEuro/source/rsaeuro.a + FCGI_OBJS = \ handle_request.fcgi.o \ main.fcgi.o \ @@ -76,6 +89,9 @@ feeder: $(FEEDER_OBJS) show_shmem: $(SHOW_SHMEM_OBJS) $(CC) $(SHOW_SHMEM_OBJS) $(MYSQL_LIBS) $(CLIBS) -o show_shmem +file_upload_handler: $(FILE_UPLOAD_OBJS) + $(CC) $(FILE_UPLOAD_OBJS) $(CLIBS) -o file_upload_handler + fcgi: $(FCGI_OBJS) $(CC) $(FCGI_OBJS) $(MYSQL_LIBS) $(CLIBS) $(FCGI_LIBS) \ -o fcgi diff --git a/sched/file_upload_handler.C b/sched/file_upload_handler.C index 4854ad46aa..476055dc25 100644 --- a/sched/file_upload_handler.C +++ b/sched/file_upload_handler.C @@ -1,11 +1,192 @@ // The input to this program looks like this: // -// -// ... +// // ... -// -// ... -// -// -// blah +// +// ... +// +// +// x +// x +// // ... (data) +// +// The return looks like +// +// 0 +// or +// 2 +// bad file size + +#include +#include + +#include "parse.h" +#include "crypt.h" + +#define BOINC_UPLOAD_DIR "/home/david/html/upload" +#define BOINC_PUBLIC_KEY_PATH "/home/david/boinc_keys/upload_public" + +#define MAX_FILES 32 + +struct FILE_INFO { + char name[256]; + double max_nbytes; + char* signature; + char* signed_xml; + int parse(FILE*); +}; + +int FILE_INFO::parse(FILE* in) { + char buf[256]; + int retval; + + memset(this, 0, sizeof(FILE_INFO)); + signed_xml = strdup(""); + while (fgets(buf, 256, in)) { + if (match_tag(buf, "")) return 0; + else if (match_tag(buf, "")) { + retval = dup_element_contents(in, "", &signature); + if (retval) return retval; + continue; + } + strcatdup(signed_xml, buf); + if (parse_str(buf, "", name)) continue; + if (parse_double(buf, "", max_nbytes)) continue; + //fprintf(stderr, "FILE_INFO::parse: unrecognized: %s \n", buf); + } + return 1; +} + +int print_status(int status, char* message) { + printf("Content-type: text/plain\n\n%d\n", status); + if (message) printf("%s\n", message); +#if 0 + fprintf(stderr, "Content-type: text/plain\n\n%d\n", status); + if (message) fprintf(stderr, "%s\n", message); +#endif + return 0; +} + +#define BLOCK_SIZE 16382 + +// read from socket, write to file +// +int read_file(FILE* in, char* path, double offset, double nbytes) { + unsigned char buf[BLOCK_SIZE]; + FILE* out; + int retval, n, m; + double bytes_left; + + out = fopen(path, "wb"); + if (!out) { + print_status(-1, "can't open file"); + return -1; + } + + // TODO: use a 64-bit variant + retval = fseek(out, (long)offset, SEEK_SET); + if (retval) { + fclose(out); + print_status(-1, "can't fseek file"); + return retval; + } + bytes_left = nbytes - offset; + while (1) { + m = BLOCK_SIZE; + if (m > bytes_left) m = (int)bytes_left; + n = fread(buf, 1, m, in); + if (n <= 0) { + print_status(-1, "can't fread socket"); + return -1; + } + m = fwrite(buf, 1, n, out); + if (m != n) { + print_status(-1, "can't fwrite file"); + return -1; + } + bytes_left -= n; + if (bytes_left == 0) break; + } + fclose(out); + return 0; +} + +int handle_request(FILE* in, R_RSA_PUBLIC_KEY& key) { + char buf[256]; + double nbytes=0, offset=0; + char path[256]; + FILE_INFO file_info; + int retval; + bool is_valid; + + while (fgets(buf, 256, in)) { + if (match_tag(buf, "")) { + retval = file_info.parse(in); + if (retval) return retval; + retval = verify_string( + file_info.signed_xml, file_info.signature, key, is_valid + ); + if (retval || !is_valid) { + print_status(-1, "invalid signature"); + return -1; + } + continue; + } + else if (parse_double(buf, "", offset)) continue; + else if (parse_double(buf, "", nbytes)) continue; + else if (match_tag(buf, "")) { + if (nbytes == 0) { + print_status(-1, "nbytes missing"); + return -1; + } + + // enforce limits in signed XML + if (nbytes > file_info.max_nbytes) { + sprintf(buf, + "nbytes too large: %f > %d", + nbytes, file_info.max_nbytes + ); + print_status(-1, buf); + return -1; + } + + sprintf(path, "%s/%s", BOINC_UPLOAD_DIR, file_info.name); + retval = read_file(in, path, offset, nbytes); + break; + } + } + return 0; +} + +int get_key(R_RSA_PUBLIC_KEY& key) { + FILE* f; + int retval; + + f = fopen(BOINC_PUBLIC_KEY_PATH, "r"); + if (!f) return -1; + retval = scan_key_hex(f, (KEY*)&key, sizeof(key)); + fclose(f); + if (retval) return retval; + return 0; +} + +int main() { + int retval; + R_RSA_PUBLIC_KEY key; + + retval = get_key(key); + if (retval) { + fprintf(stderr, "can't read key file\n"); + print_status(-1, "can't read key file"); + exit(0); + } + + retval = handle_request(stdin, key); + if (retval) { + fprintf(stderr, "handle_request: %d\n", retval); + } else { + print_status(0, 0); + } + return 0; +} diff --git a/sched/server_types.C b/sched/server_types.C index e71cc2bb6b..a869560f4c 100644 --- a/sched/server_types.C +++ b/sched/server_types.C @@ -57,10 +57,7 @@ int SCHEDULER_REQUEST::parse(FILE* fin) { else if (match_tag(buf, "")) { while (fgets(buf, 256, fin)) { if (strstr(buf, "")) break; - prefs_xml = (char*)realloc( - prefs_xml, strlen(prefs_xml) + strlen(buf)+1 - ); - strcat(prefs_xml, buf); + strcatdup(prefs_xml, buf); } } else if (match_tag(buf, "")) { diff --git a/test/1sec_result b/test/1sec_result index 40ad05f381..2be46c987e 100644 --- a/test/1sec_result +++ b/test/1sec_result @@ -2,7 +2,8 @@ - + + 100000 diff --git a/test/concat_result b/test/concat_result index 40ad05f381..2be46c987e 100644 --- a/test/concat_result +++ b/test/concat_result @@ -2,7 +2,8 @@ - + + 100000 diff --git a/test/init.inc b/test/init.inc index 5abd9de55e..f87dab58b1 100644 --- a/test/init.inc +++ b/test/init.inc @@ -23,6 +23,7 @@ $BOINC_UPLOAD_DIR = null; $BOINC_PLATFORM = null; $BOINC_EMAIL = null; $BOINC_URL_BASE = null; +$BOINC_KEY_DIR = null; function check_env_vars() { global $BOINC_DOWNLOAD_DIR; @@ -30,6 +31,7 @@ function check_env_vars() { global $BOINC_PLATFORM; global $BOINC_EMAIL; global $BOINC_URL_BASE; + global $BOINC_KEY_DIR; $bad = false; $BOINC_DOWNLOAD_DIR = getenv("BOINC_DOWNLOAD_DIR"); @@ -57,8 +59,14 @@ function check_env_vars() { echo "Must define BOINC_URL_BASE\n"; $bad = true; } + $BOINC_KEY_DIR = getenv("BOINC_KEY_DIR"); + if ($BOINC_KEY_DIR == null) { + echo "Must define BOINC_KEY_DIR\n"; + $bad = true; + } if ($bad) exit(); } + function clear_data_dirs() { global $BOINC_DOWNLOAD_DIR; global $BOINC_UPLOAD_DIR; @@ -73,10 +81,16 @@ function clear_data_dirs() { echo "Must define BOINC_UPLOAD_DIR\n"; $bad = true; } + $BOINC_KEY_DIR = getenv("BOINC_KEY_DIR"); + if ($BOINC_KEY_DIR == null) { + echo "Must define BOINC_KEY_DIR\n"; + $bad = true; + } if ($bad) exit(); PassThru("rm -f $BOINC_DOWNLOAD_DIR/*"); PassThru("rm -f $BOINC_UPLOAD_DIR/*"); + PassThru("rm -f $BOINC_KEY_DIR/*"); } function init_client_dirs($prefs_file) { @@ -143,7 +157,13 @@ function add_app($name) { } function create_work($x) { - PassThru("../tools/create_work $x"); + global $BOINC_KEY_DIR; + PassThru("../tools/create_work -keyfile $BOINC_KEY_DIR/upload_private $x"); +} + +function create_keys() { + global $BOINC_KEY_DIR; + PassThru("../lib/crypt_prog -genkey 1024 $BOINC_KEY_DIR/upload_private $BOINC_KEY_DIR/upload_public"); } function run_client($args) { diff --git a/test/master.html b/test/master.html new file mode 100644 index 0000000000..60044e2e7a --- /dev/null +++ b/test/master.html @@ -0,0 +1,3 @@ +

      Test BOINC Project

      + +http://localhost/boinc-cgi/cgi diff --git a/test/sah_result b/test/sah_result index bcd2a45a90..4e403f98dc 100644 --- a/test/sah_result +++ b/test/sah_result @@ -2,7 +2,8 @@ - + + 100000 diff --git a/test/test_1sec.php b/test/test_1sec.php index 452f0a6915..47557d9cae 100644 --- a/test/test_1sec.php +++ b/test/test_1sec.php @@ -9,6 +9,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs2.xml"); add_platform(); add_core_client(); diff --git a/test/test_concat.php b/test/test_concat.php index 71a70c4982..4054af752e 100644 --- a/test/test_concat.php +++ b/test/test_concat.php @@ -9,6 +9,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("input"); add_platform(null); diff --git a/test/test_dynamic.php b/test/test_dynamic.php index e573bad33f..b09b9864c7 100755 --- a/test/test_dynamic.php +++ b/test/test_dynamic.php @@ -8,6 +8,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("input"); add_platform(); diff --git a/test/test_max_water_prefs.php b/test/test_max_water_prefs.php index 336587015e..0684523398 100755 --- a/test/test_max_water_prefs.php +++ b/test/test_max_water_prefs.php @@ -7,6 +7,7 @@ clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("small_input"); add_platform(); diff --git a/test/test_min_water_prefs.php b/test/test_min_water_prefs.php index 759e2b35d2..23f3245e28 100755 --- a/test/test_min_water_prefs.php +++ b/test/test_min_water_prefs.php @@ -7,6 +7,7 @@ clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("small_input"); add_platform(); diff --git a/test/test_normal_water_prefs.php b/test/test_normal_water_prefs.php index 5d94a81429..37f2dbc30b 100755 --- a/test/test_normal_water_prefs.php +++ b/test/test_normal_water_prefs.php @@ -7,6 +7,7 @@ clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("small_input"); add_platform(); diff --git a/test/test_prefs.php b/test/test_prefs.php index 2b73199b21..943ce7b3d8 100644 --- a/test/test_prefs.php +++ b/test/test_prefs.php @@ -8,6 +8,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("small_input"); add_platform(); diff --git a/test/test_projects.php b/test/test_projects.php index 1f38712fcb..5c5ac7a3a0 100755 --- a/test/test_projects.php +++ b/test/test_projects.php @@ -8,6 +8,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs2.xml"); copy_to_download_dir("small_input"); add_platform(); diff --git a/test/test_stderr.php b/test/test_stderr.php index 238314d686..dbc680029b 100644 --- a/test/test_stderr.php +++ b/test/test_stderr.php @@ -8,6 +8,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("input"); add_platform(); diff --git a/test/test_uc.php b/test/test_uc.php index c7e672d8e2..c8f641193e 100644 --- a/test/test_uc.php +++ b/test/test_uc.php @@ -9,6 +9,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("input"); add_platform(null); diff --git a/test/test_uc_slow.php b/test/test_uc_slow.php index 3b9d9276f0..eb300e92da 100644 --- a/test/test_uc_slow.php +++ b/test/test_uc_slow.php @@ -9,6 +9,7 @@ check_env_vars(); clear_db(); clear_data_dirs(); + create_keys(); init_client_dirs("prefs1.xml"); copy_to_download_dir("small_input"); add_platform(null); diff --git a/test/uc_result b/test/uc_result index cff028415d..341cdea74e 100644 --- a/test/uc_result +++ b/test/uc_result @@ -2,7 +2,8 @@ - + 100000 + diff --git a/test/ucs_result b/test/ucs_result index 40ad05f381..2be46c987e 100644 --- a/test/ucs_result +++ b/test/ucs_result @@ -2,7 +2,8 @@ - + + 100000 diff --git a/tools/Makefile.in b/tools/Makefile.in index e7fd8e4ccc..903c5d5181 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -10,11 +10,16 @@ MYSQL_INC = /usr/local/mysql/include CFLAGS = -g -Wall @DEFS@ \ -DHOSTTYPE=\"@host@\" \ -DVERSION=$(VERSION) \ - -I @top_srcdir@/lib -I @top_srcdir@/db -I $(MYSQL_INC) + -I @top_srcdir@/lib \ + -I @top_srcdir@/RSAEuro/source \ + -I @top_srcdir@/db \ + -I $(MYSQL_INC) CC = @CC@ $(CFLAGS) -CLIBS = @LIBS@ +CLIBS = @LIBS@ \ + ../lib/crypt.o \ + ../RSAEuro/source/rsaeuro.a OBJS = \ backend_lib.o \ @@ -34,6 +39,7 @@ LIBS = \ process_result_template.o \ ../lib/md5_file.o \ ../lib/md5.o \ + ../lib/parse.o \ ../db/db_mysql.o \ ../db/mysql_util.o diff --git a/tools/add.C b/tools/add.C index 80a9750591..4172783152 100644 --- a/tools/add.C +++ b/tools/add.C @@ -167,7 +167,7 @@ void add_user() { strcpy(user.country, "United States"); strcpy(user.postal_code, "94703"); if (prefs_file) { - retval = read_file(prefs_file, user.prefs); + retval = read_filename(prefs_file, user.prefs); if (retval) { printf("read_file: %s", prefs_file); return; diff --git a/tools/backend_lib.C b/tools/backend_lib.C index 907e450a05..514c665f87 100644 --- a/tools/backend_lib.C +++ b/tools/backend_lib.C @@ -22,6 +22,7 @@ #include #include "db.h" +#include "crypt.h" #include "md5_file.h" #include "backend_lib.h" @@ -33,18 +34,25 @@ #define OUTFILE_MACRO "" #define DOWNLOAD_URL_MACRO "" -#define UPLOAD_URL "http://localhost/upload/" +#define UPLOAD_URL "http://localhost/boinc-cgi/file_upload_handler" #define DOWNLOAD_URL "http://localhost/download/" -int read_file(char* path, char* buf) { - FILE* f = fopen(path, "r"); - if (!f) return -1; +int read_file(FILE* f, char* buf) { int n = fread(buf, 1, MAX_BLOB_SIZE, f); buf[n] = 0; - fclose(f); return 0; } +int read_filename(char* path, char* buf) { + int retval; + + FILE* f = fopen(path, "r"); + if (!f) return -1; + retval = read_file(f, buf); + fclose(f); + return retval; +} + // replace INFILE_x with filename from array, // MD5_x with checksum of file, // WU_NAME with WU name @@ -114,10 +122,13 @@ static int process_wu_template( return 0; } -int create_result(WORKUNIT& wu, char* result_template, int i) { +int create_result( + WORKUNIT& wu, char* result_template_filename, int i, R_RSA_PRIVATE_KEY& key +) { RESULT r; char base_outfile_name[256]; int retval; + FILE* result_template_file, *tempfile; memset(&r, 0, sizeof(r)); r.create_time = time(0); @@ -125,23 +136,35 @@ int create_result(WORKUNIT& wu, char* result_template, int i) { r.state = RESULT_STATE_UNSENT; sprintf(r.name, "%s_%d", wu.name, i); sprintf(base_outfile_name, "%s_", r.name); - strcpy(r.xml_doc_in, result_template); + + result_template_file = fopen(result_template_filename, "r"); + tempfile = tmpfile(); retval = process_result_template( - r.xml_doc_in, base_outfile_name, wu.name, r.name + result_template_file, + tempfile, + key, + base_outfile_name, wu.name, r.name ); + rewind(tempfile); + read_file(tempfile, r.xml_doc_in); + fclose(tempfile); retval = db_result_new(r); + if (retval) { + fprintf(stderr, "db_result_new: %d\n", retval); + } return retval; } int create_work( WORKUNIT& wu, char* wu_template, - char* result_template, + char* result_template_file, int nresults, char* infile_dir, char** infiles, - int ninfiles + int ninfiles, + R_RSA_PRIVATE_KEY& key ) { int i, retval; @@ -160,7 +183,7 @@ int create_work( if (!wu.dynamic_results) { for (i=0; i #include "db.h" +#include "crypt.h" #include "backend_lib.h" int main(int argc, char** argv) { @@ -52,8 +54,10 @@ int main(int argc, char** argv) { char wu_template[MAX_BLOB_SIZE]; char result_template[MAX_BLOB_SIZE]; char wu_template_file[256], result_template_file[256]; + char keyfile[256]; char** infiles; int i, ninfiles, nresults; + R_RSA_PRIVATE_KEY key; char* boinc_download_dir = getenv("BOINC_DOWNLOAD_DIR"); srand(time(NULL)); @@ -68,6 +72,7 @@ int main(int argc, char** argv) { strcpy(wu_template_file, ""); strcpy(result_template_file, ""); strcpy(app.name, ""); + strcpy(keyfile, ""); nresults = 1; i = 1; ninfiles = 0; @@ -101,7 +106,10 @@ int main(int argc, char** argv) { wu.rsc_memory = atof(argv[i+1]); } else if (!strcmp(argv[i], "-rsc_disk")) { i++; - wu.rsc_disk = atof(argv[i+1]); + wu.rsc_disk = atof(argv[i]); + } else if (!strcmp(argv[i], "-keyfile")) { + i++; + strcpy(keyfile, argv[i]); } else if (!strcmp(argv[i], "-wu_name_rand")) { i++; sprintf(wu.name, "%s_%d", argv[i], rand()); @@ -124,25 +132,51 @@ int main(int argc, char** argv) { exit(1); } - retval = read_file(wu_template_file, wu_template); - if (retval) {fprintf(stderr, "can't open WU template\n"); exit(1); } - retval = read_file(result_template_file, result_template); - if (retval) {fprintf(stderr, "can't open result template\n"); exit(1); } + retval = read_filename(wu_template_file, wu_template); + if (retval) { + fprintf(stderr, "can't open WU template\n"); + exit(1); + } - if (wu.dynamic_results) strcpy(app.result_xml_template, result_template); - retval = db_app_update(app); - if (retval) printf("db_app_update: %d\n", retval); +#if 0 + retval = read_file(result_template_file, result_template); + if (retval) { + fprintf(stderr, "can't open result template\n"); + exit(1); + } +#endif + + if (wu.dynamic_results) { + strcpy(app.result_xml_template, result_template); + retval = db_app_update(app); + if (retval) printf("db_app_update: %d\n", retval); + } wu.appid = app.id; + + + FILE* fkey = fopen(keyfile, "r"); + if (!fkey) { + printf("create_work: can't open key file (%s)\n", keyfile); + exit(1); + } + retval = scan_key_hex(fkey, (KEY*)&key, sizeof(key)); + fclose(fkey); + if (retval) { + printf("can't parse key\n"); + exit(1); + } + retval = create_work( wu, wu_template, - result_template, + result_template_file, nresults, boinc_download_dir, infiles, - ninfiles + ninfiles, + key ); - if (retval) printf("create_work: %d\n", retval); + if (retval) fprintf(stderr, "create_work: %d\n", retval); db_close(); } diff --git a/tools/process_result_template.C b/tools/process_result_template.C index f2563e95d9..13f7b3891b 100644 --- a/tools/process_result_template.C +++ b/tools/process_result_template.C @@ -17,74 +17,115 @@ // Contributor(s): // +// macro-substitute a result template file: +// - replace OUTFILE_x with base_filename_x, +// - WU_NAME with WU name +// - RESULT_NAME with result name +// - At the end of every element, add a signature +// of its contents up to that point. + #include #include #include "db.h" +#include "parse.h" +#include "crypt.h" #define WU_NAME_MACRO "" #define RESULT_NAME_MACRO "" #define OUTFILE_MACRO "" #define DOWNLOAD_URL_MACRO "" -#define UPLOAD_URL "http://localhost/upload/" +#define UPLOAD_URL "http://localhost/boinc-cgi/file_upload_handler" #define DOWNLOAD_URL "http://localhost/download/" -// replace OUTFILE_x with base_filename_x, -// WU_NAME with WU name -// RESULT_NAME with result name -// int process_result_template( - char* out, char* base_filename, char* wu_name, char* result_name + FILE* in, FILE* out, + R_RSA_PRIVATE_KEY& key, + char* base_filename, char* wu_name, char* result_name ) { - char* p,*q; - char buf[MAX_BLOB_SIZE]; + char* p,*q, *signed_xml=strdup(""); + char buf[256], temp[256]; + unsigned char signature_buf[SIGNATURE_SIZE]; + DATA_BLOCK block, signature; char num; int i; bool found; - while (1) { - found = false; - p = strstr(out, OUTFILE_MACRO); - if (p) { - found = true; - i = atoi(p+strlen(OUTFILE_MACRO)); - q = p+strlen(OUTFILE_MACRO); - num = q[0]; - strcpy(buf, p+strlen(OUTFILE_MACRO)+1+2); - strcpy(p, base_filename); - strncat(p, &num, 1); - strcat(p, buf); + + while (fgets(buf, 256, in)) { + + // when we reach the end of a element, + // generate a signature for the contents thus far + // + if (match_tag(buf, "")) { + free(signed_xml); + signed_xml = strdup(""); + fputs(buf, out); + continue; } - p = strstr(out, UPLOAD_URL_MACRO); - if (p) { - found = true; - strcpy(buf, p+strlen(UPLOAD_URL_MACRO)); - strcpy(p, UPLOAD_URL); - strcat(p, buf); + if (match_tag(buf, "")) { + block.data = (unsigned char*)signed_xml; + block.len = strlen(signed_xml); + signature.data = signature_buf; + signature.len = SIGNATURE_SIZE; + sign_block(block, key, signature); + fprintf(out, "\n"); + print_hex_data(out, signature); + printf("signing [\n%s]\n", signed_xml); + printf("signature: [\n"); + print_hex_data(stdout, signature); + printf("]\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + continue; } - p = strstr(out, DOWNLOAD_URL_MACRO); - if (p) { - found = true; - strcpy(buf, p+strlen(DOWNLOAD_URL_MACRO)); - strcpy(p, DOWNLOAD_URL); - strcat(p, buf); + + while (1) { + found = false; + p = strstr(buf, OUTFILE_MACRO); + if (p) { + found = true; + i = atoi(p+strlen(OUTFILE_MACRO)); + q = p+strlen(OUTFILE_MACRO); + num = q[0]; + strcpy(temp, p+strlen(OUTFILE_MACRO)+1+2); + strcpy(p, base_filename); + strncat(p, &num, 1); + strcat(p, temp); + } + p = strstr(buf, UPLOAD_URL_MACRO); + if (p) { + found = true; + strcpy(temp, p+strlen(UPLOAD_URL_MACRO)); + strcpy(p, UPLOAD_URL); + strcat(p, temp); + } + p = strstr(buf, DOWNLOAD_URL_MACRO); + if (p) { + found = true; + strcpy(temp, p+strlen(DOWNLOAD_URL_MACRO)); + strcpy(p, DOWNLOAD_URL); + strcat(p, temp); + } + p = strstr(buf, WU_NAME_MACRO); + if (p) { + found = true; + strcpy(temp, p+strlen(WU_NAME_MACRO)); + strcpy(p, wu_name); + strcat(p, temp); + } + p = strstr(buf, RESULT_NAME_MACRO); + if (p) { + found = true; + strcpy(temp, p+strlen(RESULT_NAME_MACRO)); + strcpy(p, result_name); + strcat(p, temp); + } + if (!found) break; } - p = strstr(out, WU_NAME_MACRO); - if (p) { - found = true; - strcpy(buf, p+strlen(WU_NAME_MACRO)); - strcpy(p, wu_name); - strcat(p, buf); - } - p = strstr(out, RESULT_NAME_MACRO); - if (p) { - found = true; - strcpy(buf, p+strlen(RESULT_NAME_MACRO)); - strcpy(p, result_name); - strcat(p, buf); - } - if (!found) break; + strcatdup(signed_xml, buf); + fputs(buf, out); } return 0; }