// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // GUI RPC server side (the actual RPCs) #include "cpp.h" #ifdef __APPLE__ #include #endif #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #include #if HAVE_UNISTD_H #include #endif #if HAVE_SYS_SOCKET_H #include #endif #if HAVE_SYS_UN_H #include #endif #include #include #if HAVE_NETINET_IN_H #include #endif #if HAVE_NETINET_TCP_H #include #endif #if HAVE_ARPA_INET_H #include #endif #endif #include "error_numbers.h" #include "filesys.h" #include "network.h" #include "parse.h" #include "str_replace.h" #include "str_util.h" #include "url.h" #include "util.h" #include "client_state.h" #include "client_msgs.h" #include "client_state.h" #include "cs_proxy.h" #include "cs_notice.h" #include "file_names.h" #include "project.h" #include "result.h" using std::string; using std::vector; static void auth_failure(MIOFILE& fout) { fout.printf("\n"); } void GUI_RPC_CONN::handle_auth1(MIOFILE& fout) { sprintf(nonce, "%f", dtime()); fout.printf("%s\n", nonce); } int GUI_RPC_CONN::handle_auth2(char* buf, MIOFILE& fout) { char nonce_hash[256], nonce_hash_correct[256], buf2[512]; if (!parse_str(buf, "", nonce_hash, 256)) { auth_failure(fout); return ERR_AUTHENTICATOR; } sprintf(buf2, "%s%s", nonce, gstate.gui_rpcs.password); md5_block((const unsigned char*)buf2, (int)strlen(buf2), nonce_hash_correct); if (strcmp(nonce_hash, nonce_hash_correct)) { auth_failure(fout); return ERR_AUTHENTICATOR; } fout.printf("\n"); auth_needed = false; return 0; } // client passes its version, but ignore it for now // static void handle_exchange_versions(GUI_RPC_CONN& grc) { grc.mfout.printf( "\n" " %d\n" " %d\n" " %d\n" "\n", BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, BOINC_RELEASE ); } static void handle_get_simple_gui_info(GUI_RPC_CONN& grc) { unsigned int i; grc.mfout.printf("\n"); for (i=0; iwrite_state(grc.mfout, true); } gstate.write_tasks_gui(grc.mfout, true); grc.mfout.printf("\n"); } static void handle_get_project_status(GUI_RPC_CONN& grc) { unsigned int i; grc.mfout.printf("\n"); for (i=0; iwrite_state(grc.mfout, true); } grc.mfout.printf("\n"); } static void handle_get_disk_usage(GUI_RPC_CONN& grc) { unsigned int i; double size, boinc_non_project, d_allowed, boinc_total; grc.mfout.printf("\n"); int retval = get_filesystem_info( gstate.host_info.d_total, gstate.host_info.d_free ); if (retval) { msg_printf(0, MSG_INTERNAL_ERROR, "get_filesystem_info(): %s", boincerror(retval) ); } dir_size(".", boinc_non_project, false); dir_size("locale", size, false); boinc_non_project += size; #ifdef __APPLE__ if (gstate.launched_by_manager) { // If launched by Manager, get Manager's size on disk ProcessSerialNumber managerPSN; FSRef ourFSRef; char path[MAXPATHLEN]; double manager_size = 0.0; OSStatus err; err = GetProcessForPID(getppid(), &managerPSN); if (! err) err = GetProcessBundleLocation(&managerPSN, &ourFSRef); if (! err) err = FSRefMakePath (&ourFSRef, (UInt8*)path, sizeof(path)); if (! err) dir_size(path, manager_size, true); if (! err) boinc_non_project += manager_size; } #endif boinc_total = boinc_non_project; gstate.get_disk_usages(); for (i=0; i\n" " %s\n" " %f\n" "\n", p->master_url, p->disk_usage ); boinc_total += p->disk_usage; } d_allowed = gstate.allowed_disk_usage(gstate.total_disk_usage); grc.mfout.printf( "%f\n" "%f\n" "%f\n" "%f\n", gstate.host_info.d_total, gstate.host_info.d_free, boinc_non_project, d_allowed ); grc.mfout.printf("\n"); } static PROJECT* get_project(GUI_RPC_CONN& grc, string url) { if (url.empty()) { grc.mfout.printf("Missing project URL\n"); return 0; } PROJECT* p = gstate.lookup_project(url.c_str()); if (!p) { grc.mfout.printf("No such project\n"); return 0 ; } return p; } static PROJECT* get_project_parse(GUI_RPC_CONN& grc) { string url; while (!grc.xp.get_tag()) { if (grc.xp.parse_string("project_url", url)) continue; } return get_project(grc, url); } static void handle_project_reset(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); gstate.request_schedule_cpus("project reset by user"); gstate.request_work_fetch("project reset by user"); gstate.reset_project(p, false); grc.mfout.printf("\n"); } static void handle_project_suspend(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "project suspended by user"); p->suspend(); grc.mfout.printf("\n"); } static void handle_project_resume(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "project resumed by user"); p->resume(); grc.mfout.printf("\n"); } static void handle_project_detach(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); if (p->attached_via_acct_mgr) { msg_printf(p, MSG_INFO, "This project must be detached using the account manager web site." ); grc.mfout.printf("must detach using account manager"); return; } gstate.detach_project(p); gstate.request_schedule_cpus("project detached by user"); gstate.request_work_fetch("project detached by user"); grc.mfout.printf("\n"); } static void handle_project_update(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "update requested by user"); p->sched_rpc_pending = RPC_REASON_USER_REQ; p->min_rpc_time = 0; #if 1 rss_feeds.trigger_fetch(p); #endif gstate.request_work_fetch("project updated by user"); grc.mfout.printf("\n"); } static void handle_project_nomorework(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "work fetch suspended by user"); p->dont_request_more_work = true; grc.mfout.printf("\n"); } static void handle_project_allowmorework(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "work fetch resumed by user"); p->dont_request_more_work = false; gstate.request_work_fetch("project work fetch resumed by user"); grc.mfout.printf("\n"); } static void handle_project_detach_when_done(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "detach when done set by user"); p->detach_when_done = true; p->dont_request_more_work = true; grc.mfout.printf("\n"); } static void handle_project_dont_detach_when_done(GUI_RPC_CONN& grc) { PROJECT* p = get_project_parse(grc); if (!p) return; gstate.set_client_state_dirty("Project modified by user"); msg_printf(p, MSG_INFO, "detach when done cleared by user"); p->detach_when_done = true; p->dont_request_more_work = false; grc.mfout.printf("\n"); } static void handle_set_run_mode(GUI_RPC_CONN& grc) { double duration = 0; bool btemp; int mode=-1; while (!grc.xp.get_tag()) { if (grc.xp.parse_double("duration", duration)) continue; if (grc.xp.parse_bool("always", btemp)) { mode = RUN_MODE_ALWAYS; continue; } if (grc.xp.parse_bool("never", btemp)) { mode = RUN_MODE_NEVER; continue; } if (grc.xp.parse_bool("auto", btemp)) { mode = RUN_MODE_AUTO; continue; } if (grc.xp.parse_bool("restore", btemp)) { mode = RUN_MODE_RESTORE; continue; } } if (mode < 0) { grc.mfout.printf("Missing mode\n"); return; } gstate.cpu_run_mode.set(mode, duration); grc.mfout.printf("\n"); } static void handle_set_gpu_mode(GUI_RPC_CONN& grc) { double duration = 0; bool btemp; int mode=-1; while (!grc.xp.get_tag()) { if (grc.xp.parse_double("duration", duration)) continue; if (grc.xp.parse_bool("always", btemp)) { mode = RUN_MODE_ALWAYS; continue; } if (grc.xp.parse_bool("never", btemp)) { mode = RUN_MODE_NEVER; continue; } if (grc.xp.parse_bool("auto", btemp)) { mode = RUN_MODE_AUTO; continue; } if (grc.xp.parse_bool("restore", btemp)) { mode = RUN_MODE_RESTORE; continue; } } if (mode < 0) { grc.mfout.printf("Missing mode\n"); return; } gstate.gpu_run_mode.set(mode, duration); gstate.request_schedule_cpus("GPU mode changed"); grc.mfout.printf("\n"); } static void handle_set_host_info(GUI_RPC_CONN& grc) { while (!grc.xp.get_tag()) { if (grc.xp.match_tag("host_info")) { HOST_INFO hi; int retval = hi.parse(grc.xp); if (retval) { grc.mfout.printf("host_info parse error\n"); return; } if (strlen(hi.product_name)) { safe_strcpy(gstate.host_info.product_name, hi.product_name); } grc.mfout.printf("\n"); gstate.set_client_state_dirty("set_host_info RPC"); return; } } grc.mfout.printf("Missing host_info\n"); } static void handle_set_network_mode(GUI_RPC_CONN& grc) { double duration = 0; bool btemp; int mode=-1; while (!grc.xp.get_tag()) { if (grc.xp.parse_double("duration", duration)) continue; if (grc.xp.parse_bool("always", btemp)) { mode = RUN_MODE_ALWAYS; continue; } if (grc.xp.parse_bool("never", btemp)) { mode = RUN_MODE_NEVER; continue; } if (grc.xp.parse_bool("auto", btemp)) { mode = RUN_MODE_AUTO; continue; } if (grc.xp.parse_bool("restore", btemp)) { mode = RUN_MODE_RESTORE; continue; } } if (mode < 0) { grc.mfout.printf("Missing mode\n"); return; } // user is turning network on/off explicitly, // so disable the "5 minute grace period" mechanism // gstate.gui_rpcs.time_of_last_rpc_needing_network = 0; gstate.network_run_mode.set(mode, duration); grc.mfout.printf("\n"); } static void handle_run_benchmarks(GUI_RPC_CONN& grc) { gstate.start_cpu_benchmarks(); grc.mfout.printf("\n"); } static void handle_set_proxy_settings(GUI_RPC_CONN& grc) { gui_proxy_info.parse(grc.xp); gstate.set_client_state_dirty("Set proxy settings RPC"); grc.mfout.printf("\n"); select_proxy_info(); // tell running apps to reread app_info file (for F@h) // gstate.active_tasks.request_reread_app_info(); } static void handle_get_proxy_settings(GUI_RPC_CONN& grc) { gui_proxy_info.write(grc.mfout); } // params: // [ n ] // return only msgs with seqno > n; if absent or zero, return all // static void handle_get_messages(GUI_RPC_CONN& grc) { int seqno=0; bool translatable = false; while (!grc.xp.get_tag()) { if (grc.xp.parse_int("seqno", seqno)) continue; if (grc.xp.parse_bool("translatable", translatable)) continue; } message_descs.write(seqno, grc.mfout, translatable); } static void handle_get_message_count(GUI_RPC_CONN& grc) { grc.mfout.printf("%d\n", message_descs.highest_seqno()); } // // XXX // XXX // // static void handle_file_transfer_op(GUI_RPC_CONN& grc, const char* op) { string project_url, filename; while (!grc.xp.get_tag()) { if (grc.xp.parse_string("filename", filename)) continue; if (grc.xp.parse_string("project_url", project_url)) continue; } PROJECT* p = get_project(grc, project_url); if (!p) return; if (filename.empty()) { grc.mfout.printf("Missing filename\n"); return; } FILE_INFO* f = gstate.lookup_file_info(p, filename.c_str()); if (!f) { grc.mfout.printf("No such file\n"); return; } PERS_FILE_XFER* pfx = f->pers_file_xfer; if (!pfx) { grc.mfout.printf("No such transfer waiting\n"); return; } if (!strcmp(op, "retry")) { pfx->next_request_time = 0; // leave file-level backoff mode f->project->file_xfer_backoff(pfx->is_upload).file_xfer_succeeded(); // and leave project-level backoff mode } else if (!strcmp(op, "abort")) { f->pers_file_xfer->abort(); } else { grc.mfout.printf("unknown op\n"); return; } gstate.set_client_state_dirty("File transfer RPC"); grc.mfout.printf("\n"); } static void handle_retry_file_transfer(GUI_RPC_CONN& grc) { handle_file_transfer_op(grc, "retry"); } static void handle_abort_file_transfer(GUI_RPC_CONN& grc) { handle_file_transfer_op(grc, "abort"); } static void handle_result_op(GUI_RPC_CONN& grc, const char* op) { RESULT* rp; char result_name[256]; ACTIVE_TASK* atp; string project_url; strcpy(result_name, ""); while (!grc.xp.get_tag()) { if (grc.xp.parse_str("name", result_name, sizeof(result_name))) continue; if (grc.xp.parse_string("project_url", project_url)) continue; } PROJECT* p = get_project(grc, project_url); if (!p) return; if (!strlen(result_name)) { grc.mfout.printf("Missing result name\n"); return; } rp = gstate.lookup_result(p, result_name); if (!rp) { grc.mfout.printf("no such result\n"); return; } if (!strcmp(op, "abort")) { msg_printf(p, MSG_INFO, "task %s aborted by user", result_name); atp = gstate.lookup_active_task_by_result(rp); if (atp) { atp->abort_task(EXIT_ABORTED_VIA_GUI, "aborted by user"); } else { rp->abort_inactive(EXIT_ABORTED_VIA_GUI); } gstate.request_work_fetch("result aborted by user"); } else if (!strcmp(op, "suspend")) { msg_printf(p, MSG_INFO, "task %s suspended by user", result_name); rp->suspended_via_gui = true; gstate.request_work_fetch("task suspended by user"); } else if (!strcmp(op, "resume")) { msg_printf(p, MSG_INFO, "task %s resumed by user", result_name); rp->suspended_via_gui = false; } gstate.request_schedule_cpus("task suspended, resumed or aborted by user"); gstate.set_client_state_dirty("Result RPC"); grc.mfout.printf("\n"); } static void handle_suspend_result(GUI_RPC_CONN& grc) { handle_result_op(grc, "suspend"); } static void handle_resume_result(GUI_RPC_CONN& grc) { handle_result_op(grc, "resume"); } static void handle_abort_result(GUI_RPC_CONN& grc) { handle_result_op(grc, "abort"); } static void handle_get_host_info(GUI_RPC_CONN& grc) { gstate.host_info.write(grc.mfout, true, true); } static void handle_get_screensaver_tasks(GUI_RPC_CONN& grc) { unsigned int i; ACTIVE_TASK* atp; grc.mfout.printf( "\n" " %d\n", gstate.suspend_reason ); for (i=0; itask_state() == PROCESS_EXECUTING) || ((atp->task_state() == PROCESS_SUSPENDED) && (gstate.suspend_reason == SUSPEND_REASON_CPU_THROTTLE))) { atp->result->write_gui(grc.mfout); } } grc.mfout.printf("\n"); } static void handle_quit(GUI_RPC_CONN& grc) { gstate.requested_exit = true; grc.mfout.printf("\n"); } static void handle_acct_mgr_info(GUI_RPC_CONN& grc) { grc.mfout.printf( "\n" " %s\n" " %s\n", gstate.acct_mgr_info.master_url, gstate.acct_mgr_info.project_name ); if (strlen(gstate.acct_mgr_info.login_name)) { grc.mfout.printf(" \n"); } if (gstate.acct_mgr_info.cookie_required) { grc.mfout.printf(" \n"); grc.mfout.printf( " %s\n", gstate.acct_mgr_info.cookie_failure_url ); } grc.mfout.printf("\n"); } static void handle_get_statistics(GUI_RPC_CONN& grc) { grc.mfout.printf("\n"); for (unsigned int i=0; iwrite_statistics(grc.mfout); } grc.mfout.printf("\n"); } static void handle_get_cc_status(GUI_RPC_CONN& grc) { grc.mfout.printf( "\n" " %d\n" " %d\n" " %d\n" " %d\n" " %d\n" " %f\n" " %d\n" " %d\n" " %d\n" " %f\n" " %d\n" " %d\n" " %d\n" " %f\n" " %d\n" " %d\n" " %d\n", net_status.network_status(), gstate.acct_mgr_info.password_error?1:0, gstate.suspend_reason, gstate.cpu_run_mode.get_current(), gstate.cpu_run_mode.get_perm(), gstate.cpu_run_mode.delay(), gpu_suspend_reason, gstate.gpu_run_mode.get_current(), gstate.gpu_run_mode.get_perm(), gstate.gpu_run_mode.delay(), gstate.network_suspend_reason, gstate.network_run_mode.get_current(), gstate.network_run_mode.get_perm(), gstate.network_run_mode.delay(), config.disallow_attach?1:0, config.simple_gui_only?1:0, config.max_event_log_lines ); if (grc.au_mgr_state == AU_MGR_QUIT_REQ) { grc.mfout.printf( " 1\n" ); grc.au_mgr_state = AU_MGR_QUIT_SENT; } grc.mfout.printf( "\n" ); } static void handle_network_available(GUI_RPC_CONN& grc) { net_status.network_available(); grc.mfout.printf("\n"); } static void handle_get_project_init_status(GUI_RPC_CONN& grc) { // If we're already attached to the project specified in the // project init file, delete the file. // for (unsigned i=0; imaster_url, gstate.project_init.url)) { gstate.project_init.remove(); break; } } grc.mfout.printf( "\n" " %s\n" " %s\n" " %s\n" " %s\n" "\n", gstate.project_init.url, gstate.project_init.name, gstate.project_init.team_name, strlen(gstate.project_init.account_key)?"":"" ); } void handle_get_project_config(GUI_RPC_CONN& grc) { string url; while (!grc.xp.get_tag()) { if (grc.xp.parse_string("url", url)) continue; } if (url.empty()) { grc.mfout.printf("no url\n"); return; } canonicalize_master_url(url); grc.get_project_config_op.do_rpc(url); grc.mfout.printf("\n"); } void handle_get_project_config_poll(GUI_RPC_CONN& grc) { if (grc.get_project_config_op.error_num) { grc.mfout.printf( "\n" " %d\n" "\n", grc.get_project_config_op.error_num ); } else { grc.mfout.printf("%s", grc.get_project_config_op.reply.c_str()); } } void handle_lookup_account(GUI_RPC_CONN& grc) { ACCOUNT_IN ai; MIOFILE in; ai.parse(grc.xp); if (!ai.url.size() || !ai.email_addr.size() || !ai.passwd_hash.size()) { grc.mfout.printf("missing URL, email address, or password\n"); return; } grc.lookup_account_op.do_rpc(ai); grc.mfout.printf("\n"); } void handle_lookup_account_poll(GUI_RPC_CONN& grc) { if (grc.lookup_account_op.error_num) { grc.mfout.printf( "\n" " %d\n" "\n", grc.lookup_account_op.error_num ); } else { grc.mfout.printf("%s", grc.lookup_account_op.reply.c_str()); } } void handle_create_account(GUI_RPC_CONN& grc) { ACCOUNT_IN ai; ai.parse(grc.xp); grc.create_account_op.do_rpc(ai); grc.mfout.printf("\n"); } void handle_create_account_poll(GUI_RPC_CONN& grc) { if (grc.create_account_op.error_num) { grc.mfout.printf( "\n" " %d\n" "\n", grc.create_account_op.error_num ); } else { grc.mfout.printf("%s", grc.create_account_op.reply.c_str()); } } static void handle_project_attach(GUI_RPC_CONN& grc) { string url, authenticator, project_name; bool use_config_file = false; bool already_attached = false; unsigned int i; int retval; while (!grc.xp.get_tag()) { if (grc.xp.parse_bool("use_config_file", use_config_file)) continue; if (grc.xp.parse_string("project_url", url)) continue; if (grc.xp.parse_string("authenticator", authenticator)) continue; if (grc.xp.parse_string("project_name", project_name)) continue; } // Get URL/auth from project_init.xml? // if (use_config_file) { if (!strlen(gstate.project_init.url)) { grc.mfout.printf("Missing URL\n"); return; } if (!strlen(gstate.project_init.account_key)) { grc.mfout.printf("Missing authenticator\n"); return; } url = gstate.project_init.url; authenticator = gstate.project_init.account_key; } else { if (url.empty()) { grc.mfout.printf("Missing URL\n"); return; } if (authenticator.empty()) { grc.mfout.printf("Missing authenticator\n"); return; } } for (i=0; imaster_url) already_attached = true; } if (already_attached) { grc.mfout.printf("Already attached to project\n"); return; } // clear messages from previous attach to project. // gstate.project_attach.messages.clear(); gstate.project_attach.error_num = gstate.add_project( url.c_str(), authenticator.c_str(), project_name.c_str(), false ); // if project_init.xml refers to this project, // delete the file, otherwise we'll just // reattach the next time the client starts // if (!strcmp(url.c_str(), gstate.project_init.url)) { retval = gstate.project_init.remove(); if (retval) { msg_printf(NULL, MSG_INTERNAL_ERROR, "Can't delete project init file: %s", boincerror(retval) ); } } grc.mfout.printf("\n"); } static void handle_project_attach_poll(GUI_RPC_CONN& grc) { unsigned int i; grc.mfout.printf( "\n" ); for (i=0; i%s\n", gstate.project_attach.messages[i].c_str() ); } grc.mfout.printf( " %d\n", gstate.project_attach.error_num ); grc.mfout.printf( "\n" ); } static void handle_acct_mgr_rpc(GUI_RPC_CONN& grc) { string url, name, password; string password_hash, name_lc; bool use_config_file = false; bool bad_arg = false; bool url_found=false, name_found=false, password_found = false; while (!grc.xp.get_tag()) { if (grc.xp.parse_string("url", url)) { url_found = true; continue; } if (grc.xp.parse_string("name", name)) { name_found = true; continue; } if (grc.xp.parse_string("password", password)) { password_found = true; continue; } if (grc.xp.parse_bool("use_config_file", use_config_file)) continue; } if (!use_config_file) { bad_arg = !url_found || !name_found || !password_found; if (!bad_arg) { name_lc = name; downcase_string(name_lc); if (!starts_with(password, "hash:")) { password_hash = md5_string(password+name_lc); } else { // Remove 'hash:' password_hash = password.substr(5); } } } else { if (!strlen(gstate.acct_mgr_info.master_url)) { bad_arg = true; msg_printf(NULL, MSG_INTERNAL_ERROR, "Account manager info missing from config file" ); } else { url = gstate.acct_mgr_info.master_url; name = gstate.acct_mgr_info.login_name; password_hash = gstate.acct_mgr_info.password_hash; } } if (bad_arg) { grc.mfout.printf("bad arg\n"); } else { gstate.acct_mgr_op.do_rpc(url, name, password_hash, true); grc.mfout.printf("\n"); } } static void handle_acct_mgr_rpc_poll(GUI_RPC_CONN& grc) { grc.mfout.printf( "\n" ); if (gstate.acct_mgr_op.error_str.size()) { grc.mfout.printf( " %s\n", gstate.acct_mgr_op.error_str.c_str() ); } grc.mfout.printf( " %d\n", gstate.acct_mgr_op.error_num ); grc.mfout.printf( "\n" ); } static void handle_get_newer_version(GUI_RPC_CONN& grc) { grc.mfout.printf( "%s\n" "%s\n", gstate.newer_version.c_str(), config.client_download_url.c_str() ); } static void handle_get_global_prefs_file(GUI_RPC_CONN& grc) { GLOBAL_PREFS p; bool found; int retval = p.parse_file( GLOBAL_PREFS_FILE_NAME, gstate.main_host_venue, found ); if (retval) { grc.mfout.printf("%d\n", retval); return; } p.write(grc.mfout); } static void handle_get_global_prefs_working(GUI_RPC_CONN& grc) { gstate.global_prefs.write(grc.mfout); } static void handle_get_global_prefs_override(GUI_RPC_CONN& grc) { string s; int retval = read_file_string(GLOBAL_PREFS_OVERRIDE_FILE, s); if (!retval) { strip_whitespace(s); grc.mfout.printf("%s\n", s.c_str()); } else { grc.mfout.printf("no prefs override file\n"); } } static void handle_set_global_prefs_override(GUI_RPC_CONN& grc) { int retval; char buf[65536]; retval = grc.xp.element_contents("", buf, sizeof(buf)); if (!retval) { if (strlen(buf)) { FILE* f = boinc_fopen(GLOBAL_PREFS_OVERRIDE_FILE, "w"); if (f) { fprintf(f, "%s\n", buf); fclose(f); retval = 0; } else { retval = ERR_FOPEN; } } else { retval = boinc_delete_file(GLOBAL_PREFS_OVERRIDE_FILE); } } grc.mfout.printf( "\n" " %d\n" "\n", retval ); } static void handle_get_cc_config(GUI_RPC_CONN& grc) { string s; int retval = read_file_string(CONFIG_FILE, s); if (!retval) { strip_whitespace(s); grc.mfout.printf("%s\n", s.c_str()); } } static void read_all_projects_list_file(GUI_RPC_CONN& grc) { string s; int retval = read_file_string(ALL_PROJECTS_LIST_FILENAME, s); if (!retval) { strip_whitespace(s); grc.mfout.printf("%s\n", s.c_str()); } } static void handle_get_state(GUI_RPC_CONN& grc) { gstate.write_state_gui(grc.mfout); } static void handle_set_cc_config(GUI_RPC_CONN& grc) { int retval; char buf[65536]; retval = grc.xp.element_contents("", buf, sizeof(buf)); if (!retval) { if (strlen(buf)) { FILE* f = boinc_fopen(CONFIG_FILE, "w"); if (f) { fprintf(f, "%s\n", buf); fclose(f); retval = 0; } else { retval = ERR_FOPEN; } } else { retval = boinc_delete_file(CONFIG_FILE); } } grc.mfout.printf( "\n" " %d\n" "\n", retval ); } static void handle_get_notices(GUI_RPC_CONN& grc) { int seqno = 0; while (!grc.xp.get_tag()) { if (grc.xp.parse_int("seqno", seqno)) continue; } notices.write(seqno, grc, false); } static void handle_get_notices_public(GUI_RPC_CONN& grc) { int seqno = 0; while (!grc.xp.get_tag()) { if (grc.xp.parse_int("seqno", seqno)) continue; } notices.write(seqno, grc, true); } static void handle_get_results(GUI_RPC_CONN& grc) { bool active_only = false; while (!grc.xp.get_tag()) { if (grc.xp.parse_bool("active_only", active_only)) continue; } grc.mfout.printf("\n"); gstate.write_tasks_gui(grc.mfout, active_only); grc.mfout.printf("\n"); } static void handle_get_all_projects_list(GUI_RPC_CONN& grc) { read_all_projects_list_file(grc); } static void handle_get_file_transfers(GUI_RPC_CONN& grc) { gstate.write_file_transfers_gui(grc.mfout); } static void handle_read_global_prefs_override(GUI_RPC_CONN& grc) { grc.mfout.printf("\n"); gstate.read_global_prefs(); gstate.request_schedule_cpus("Preferences override"); gstate.request_work_fetch("Preferences override"); } static void handle_read_cc_config(GUI_RPC_CONN& grc) { grc.mfout.printf("\n"); read_config_file(false); config.show(); log_flags.show(); gstate.set_ncpus(); process_gpu_exclusions(); // also reread app_config.xml files // check_app_config(); gstate.request_schedule_cpus("Core client configuration"); gstate.request_work_fetch("Core client configuration"); set_no_rsc_config(); } static void handle_get_daily_xfer_history(GUI_RPC_CONN& grc) { daily_xfer_history.write_xml(grc.mfout); } static bool complete_post_request(char* buf) { if (strncmp(buf, "POST", 4)) return false; char* p = strstr(buf, "Content-Length: "); if (!p) return false; p += strlen("Content-Length: "); int n = atoi(p); p = strstr(p, "\r\n\r\n"); if (!p) return false; p += 4; if ((int)strlen(p) < n) return false; return true; } static void handle_report_device_status(GUI_RPC_CONN& grc) { DEVICE_STATUS d; while (!grc.xp.get_tag()) { if (grc.xp.match_tag("device_status")) { int retval = d.parse(grc.xp); if (log_flags.android_debug) { if (retval) { msg_printf(0, MSG_INFO, "report_device_status RPC parse failed: %d", retval ); } else { msg_printf(0, MSG_INFO, "Android device status:" ); msg_printf(0, MSG_INFO, "On AC: %s; on USB: %s; on WiFi: %s", d.on_ac_power?"yes":"no", d.on_usb_power?"yes":"no", d.wifi_online?"yes":"no" ); msg_printf(0, MSG_INFO, "Battery: charge pct: %f; temp %f state %s", d.battery_charge_pct, d.battery_temperature_celsius, battery_state_string(d.battery_state) ); } } if (!retval) { gstate.device_status = d; gstate.device_status_time = gstate.now; grc.mfout.printf("\n"); return; } } } grc.mfout.printf("\n"); } int DEVICE_STATUS::parse(XML_PARSER& xp) { while (!xp.get_tag()) { if (xp.match_tag("/device_status")) { return 0; } if (xp.parse_bool("on_ac_power", on_ac_power)) continue; if (xp.parse_bool("on_usb_power", on_usb_power)) continue; if (xp.parse_double("battery_charge_pct", battery_charge_pct)) continue; if (xp.parse_int("battery_state", battery_state)) continue; if (xp.parse_double("battery_temperature_celsius", battery_temperature_celsius)) continue; if (xp.parse_bool("wifi_online", wifi_online)) continue; } return ERR_XML_PARSE; } // Some of the RPCs have empty-element request messages. // We accept both and // #define match_req(buf, tag) (match_tag(buf, "<" tag ">") || match_tag(buf, "<" tag "/>")) typedef void (*GUI_RPC_HANDLER)(GUI_RPC_CONN&); struct GUI_RPC { const char* req_tag; char alt_req_tag[256]; GUI_RPC_HANDLER handler; bool auth_required; // operations that require authentication only for non-local clients. // Use this only for information that should be available to people // sharing this computer (e.g. what jobs are running) // but not for anything sensitive (passwords etc.) bool enable_network; // RPCs that should enable network communication for 5 minutes, // overriding other factors. // Things like attaching projects, etc. bool read_only; // doesn't modify the client's data structs GUI_RPC(const char* req, GUI_RPC_HANDLER h, bool ar, bool en, bool ro) { req_tag = req; safe_strcpy(alt_req_tag, req); strcat(alt_req_tag, "/"); handler = h; auth_required = ar; enable_network = en; read_only = ro; } }; // local auth required // enable network // read-only GUI_RPC gui_rpcs[] = { GUI_RPC("exchange_versions", handle_exchange_versions, false, false, true), GUI_RPC("get_all_projects_list", handle_get_all_projects_list, false, false, true), GUI_RPC("get_cc_status", handle_get_cc_status, false, false, true), GUI_RPC("get_disk_usage", handle_get_disk_usage, false, false, true), GUI_RPC("get_daily_xfer_history", handle_get_daily_xfer_history, false, false, true), GUI_RPC("get_file_transfers", handle_get_file_transfers, false, false, true), GUI_RPC("get_host_info", handle_get_host_info, false, false, true), GUI_RPC("get_messages", handle_get_messages, false, false, true), GUI_RPC("get_message_count", handle_get_message_count, false, false, true), GUI_RPC("get_newer_version", handle_get_newer_version, false, false, true), GUI_RPC("get_notices_public", handle_get_notices_public, false, false, true), GUI_RPC("get_project_status", handle_get_project_status, false, false, true), GUI_RPC("get_results", handle_get_results, false, false, true), GUI_RPC("get_screensaver_tasks", handle_get_screensaver_tasks, false, false, true), GUI_RPC("get_simple_gui_info", handle_get_simple_gui_info, false, false, true), GUI_RPC("get_state", handle_get_state, false, false, true), GUI_RPC("get_statistics", handle_get_statistics, false, false, true), // ops requiring local auth start here GUI_RPC("abort_file_transfer", handle_abort_file_transfer, true, false, false), GUI_RPC("abort_result", handle_abort_result, true, false, false), GUI_RPC("acct_mgr_info", handle_acct_mgr_info, true, false, true), GUI_RPC("get_cc_config", handle_get_cc_config, true, false, false), GUI_RPC("get_global_prefs_file", handle_get_global_prefs_file, true, false, false), GUI_RPC("get_global_prefs_override", handle_get_global_prefs_override, true, false, false), GUI_RPC("get_global_prefs_working", handle_get_global_prefs_working, true, false, false), GUI_RPC("get_notices", handle_get_notices, true, false, true), GUI_RPC("get_project_init_status", handle_get_project_init_status, true, false, false), GUI_RPC("get_proxy_settings", handle_get_proxy_settings, true, false, true), GUI_RPC("network_available", handle_network_available, true, false, false), GUI_RPC("project_allowmorework", handle_project_allowmorework, true, false, false), GUI_RPC("project_detach", handle_project_detach, true, false, false), GUI_RPC("project_detach_when_done", handle_project_detach_when_done, true, false, false), GUI_RPC("project_dont_detach_when_done", handle_project_dont_detach_when_done, true, false, false), GUI_RPC("project_nomorework", handle_project_nomorework, true, false, false), GUI_RPC("project_resume", handle_project_resume, true, false, false), GUI_RPC("project_suspend", handle_project_suspend, true, false, false), GUI_RPC("quit", handle_quit, true, false, false), GUI_RPC("read_cc_config", handle_read_cc_config, true, false, false), GUI_RPC("read_global_prefs_override", handle_read_global_prefs_override, true, false, false), GUI_RPC("report_device_status", handle_report_device_status, true, false, false), GUI_RPC("resume_result", handle_resume_result, true, false, false), GUI_RPC("run_benchmarks", handle_run_benchmarks, true, false, false), GUI_RPC("set_cc_config", handle_set_cc_config, true, false, false), GUI_RPC("set_global_prefs_override", handle_set_global_prefs_override, true, false, false), GUI_RPC("set_gpu_mode", handle_set_gpu_mode, true, false, false), GUI_RPC("set_host_info", handle_set_host_info, true, false, false), GUI_RPC("set_network_mode", handle_set_network_mode, true, false, false), GUI_RPC("set_proxy_settings", handle_set_proxy_settings, true, false, false), GUI_RPC("set_run_mode", handle_set_run_mode, true, false, false), GUI_RPC("suspend_result", handle_suspend_result, true, false, false), // ops requiring temporary network access start here GUI_RPC("acct_mgr_rpc", handle_acct_mgr_rpc, true, true, false), GUI_RPC("acct_mgr_rpc_poll", handle_acct_mgr_rpc_poll, true, true, false), GUI_RPC("create_account", handle_create_account, true, true, false), GUI_RPC("create_account_poll", handle_create_account_poll, true, true, false), GUI_RPC("get_project_config", handle_get_project_config, true, true, false), GUI_RPC("get_project_config_poll", handle_get_project_config_poll, true, true, false), GUI_RPC("lookup_account", handle_lookup_account, true, true, false), GUI_RPC("lookup_account_poll", handle_lookup_account_poll, true, true, false), GUI_RPC("project_attach", handle_project_attach, true, true, false), GUI_RPC("project_attach_poll", handle_project_attach_poll, true, true, false), GUI_RPC("project_reset", handle_project_reset, true, true, false), GUI_RPC("project_update", handle_project_update, true, true, false), GUI_RPC("retry_file_transfer", handle_retry_file_transfer, true, true, false), }; // return nonzero only if we need to close the connection // static int handle_rpc_aux(GUI_RPC_CONN& grc) { int retval = 0; grc.mfin.init_buf_read(grc.request_msg); if (grc.xp.get_tag()) return ERR_XML_PARSE; // parse if (grc.xp.get_tag()) return ERR_XML_PARSE; // parse the request tag for (unsigned int i=0; iunrecognized op: %s\n", grc.xp.parsed_tag); return 0; } // return nonzero only if we need to close the connection // int GUI_RPC_CONN::handle_rpc() { int n, retval=0; char* p; int left = GUI_RPC_REQ_MSG_SIZE - request_nbytes; #ifdef _WIN32 n = recv(sock, request_msg+request_nbytes, left, 0); #else n = read(sock, request_msg+request_nbytes, left); #endif if (n <= 0) { request_nbytes = 0; return ERR_READ; } request_nbytes += n; // buffer full? if (request_nbytes >= GUI_RPC_REQ_MSG_SIZE) { request_nbytes = 0; return ERR_READ; } request_msg[request_nbytes] = 0; if (!strncmp(request_msg, "OPTIONS", 7)) { char buf[1024]; sprintf(buf, "HTTP/1.1 200 OK\n" "Server: BOINC client\n" "Access-Control-Allow-Origin: *\n" "Access-Control-Allow-Methods: POST, GET, OPTIONS\n" "Content-Length: 0\n" "Keep-Alive: timeout=2, max=100\n" "Connection: Keep-Alive\n" "Content-Type: text/plain\n\n" ); send(sock, buf, strlen(buf), 0); request_nbytes = 0; if (log_flags.gui_rpc_debug) { msg_printf(0, MSG_INFO, "[gui_rpc] processed OPTIONS" ); } return 0; } bool http_request; if (complete_post_request(request_msg)) { http_request = true; } else { p = strchr(request_msg, 3); if (p) { *p = 0; http_request = false; } else { if (log_flags.gui_rpc_debug) { msg_printf(0, MSG_INFO, "[gui_rpc] partial GUI RPC Command = '%s'\n", request_msg ); } return 0; } } request_nbytes = 0; if (log_flags.gui_rpc_debug) { msg_printf(0, MSG_INFO, "[gui_rpc] GUI RPC Command = '%s'\n", request_msg ); } // Policy: // - the first auth failure gets an error message; after that, disconnect // - if we get an unexpected auth1 or auth2, disconnect mfout.printf("\n"); if (match_req(request_msg, "auth1")) { if (got_auth1 && auth_needed) { retval = ERR_AUTHENTICATOR; } else { handle_auth1(mfout); got_auth1 = true; } } else if (match_req(request_msg, "auth2")) { if ((!got_auth1 || got_auth2) && auth_needed) { retval = ERR_AUTHENTICATOR; } else { retval = handle_auth2(request_msg, mfout); got_auth2 = true; } } else if (auth_needed && !is_local) { auth_failure(mfout); if (sent_unauthorized) { retval = ERR_AUTHENTICATOR; } sent_unauthorized = true; } else { retval = handle_rpc_aux(*this); } mfout.printf("\n\003"); mout.get_buf(p, n); if (http_request) { char buf[1024]; sprintf(buf, "HTTP/1.1 200 OK\n" "Date: Fri, 31 Dec 1999 23:59:59 GMT\n" "Server: BOINC client\n" "Connection: close\n" "Content-Type: text/xml; charset=utf-8\n" "Content-Length: %d\n\n" "\n", n ); send(sock, buf, strlen(buf), 0); } if (p) { send(sock, p, n, 0); p[n-1]=0; // replace 003 with NULL if (log_flags.gui_rpc_debug) { if (n > 128) p[128] = 0; msg_printf(0, MSG_INFO, "[gui_rpc] GUI RPC reply: '%s'\n", p ); } free(p); } return retval; }