1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2017 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC. If not, see <http://www.gnu.org/licenses/>.
17
18 // code for communicating with account managers (AMs)
19
20 #include "cpp.h"
21
22 #ifdef _WIN32
23 #include "boinc_win.h"
24 #else
25 #include "config.h"
26 #include <cstring>
27 #endif
28
29 #ifdef _MSC_VER
30 #define snprintf _snprintf
31 #endif
32
33 #include "crypt.h"
34 #include "error_numbers.h"
35 #include "parse.h"
36 #include "str_util.h"
37 #include "str_replace.h"
38 #include "url.h"
39
40 #include "client_msgs.h"
41 #include "client_state.h"
42 #include "cs_notice.h"
43 #include "file_names.h"
44 #include "filesys.h"
45 #include "gui_http.h"
46 #include "log_flags.h"
47 #include "project.h"
48
49 #include "acct_mgr.h"
50
51 static const char *run_mode_name[] = {"", "always", "auto", "never"};
52
53 // do an account manager RPC;
54 // if URL is null, detach from current account manager
55 //
do_rpc(string _url,string name,string password_hash,bool _via_gui)56 int ACCT_MGR_OP::do_rpc(
57 string _url, string name, string password_hash,
58 bool _via_gui
59 ) {
60 int retval;
61 unsigned int i;
62 char url[256], password[256], buf[256];
63 FILE *pwdf;
64
65 strlcpy(url, _url.c_str(), sizeof(url));
66
67 error_num = ERR_IN_PROGRESS;
68 error_str = "";
69 via_gui = _via_gui;
70 if (global_prefs_xml) {
71 free(global_prefs_xml);
72 global_prefs_xml = 0;
73 }
74
75 // if null URL, detach from current AMS
76 //
77 if (!strlen(url) && strlen(gstate.acct_mgr_info.master_url)) {
78 msg_printf(NULL, MSG_INFO, "Removing account manager info");
79 gstate.acct_mgr_info.clear();
80 boinc_delete_file(ACCT_MGR_URL_FILENAME);
81 boinc_delete_file(ACCT_MGR_LOGIN_FILENAME);
82 error_num = 0;
83 for (i=0; i<gstate.projects.size(); i++) {
84 gstate.projects[i]->detach_ams();
85 }
86 ::rss_feeds.update_feed_list();
87 gstate.set_client_state_dirty("detach from AMS");
88 return 0;
89 }
90
91 canonicalize_master_url(url, sizeof(url));
92 if (!valid_master_url(url)) {
93 error_num = ERR_INVALID_URL;
94 return 0;
95 }
96
97 strlcpy(ami.master_url, url, sizeof(ami.master_url));
98 strlcpy(ami.project_name, "", sizeof(ami.project_name));
99 strlcpy(ami.login_name, name.c_str(), sizeof(ami.login_name));
100 strlcpy(ami.password_hash, password_hash.c_str(), sizeof(ami.password_hash));
101
102 FILE* f = boinc_fopen(ACCT_MGR_REQUEST_FILENAME, "w");
103 if (!f) return ERR_FOPEN;
104 fprintf(f,
105 "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
106 "<acct_mgr_request>\n"
107 " <name>%s</name>\n"
108 " <password_hash>%s</password_hash>\n"
109 " <host_cpid>%s</host_cpid>\n"
110 " <domain_name>%s</domain_name>\n"
111 " <client_version>%d.%d.%d</client_version>\n"
112 " <run_mode>%s</run_mode>\n",
113 name.c_str(), password_hash.c_str(),
114 gstate.host_info.host_cpid,
115 gstate.host_info.domain_name,
116 gstate.core_client_version.major,
117 gstate.core_client_version.minor,
118 gstate.core_client_version.release,
119 run_mode_name[gstate.cpu_run_mode.get_perm()]
120 );
121 if (strlen(gstate.acct_mgr_info.previous_host_cpid)) {
122 fprintf(f,
123 " <previous_host_cpid>%s</previous_host_cpid>\n",
124 gstate.acct_mgr_info.previous_host_cpid
125 );
126 }
127
128 // If the AMS requested it, send GUI RPC port and password hash.
129 // This is for the "farm" account manager so it
130 // can know where to send GUI RPC requests to
131 // without having to configure each host
132 //
133 if (gstate.acct_mgr_info.send_gui_rpc_info) {
134 if (gstate.cmdline_gui_rpc_port) {
135 fprintf(f," <gui_rpc_port>%d</gui_rpc_port>\n", gstate.cmdline_gui_rpc_port);
136 } else {
137 fprintf(f," <gui_rpc_port>%d</gui_rpc_port>\n", GUI_RPC_PORT);
138 }
139 if (boinc_file_exists(GUI_RPC_PASSWD_FILE)) {
140 safe_strcpy(password, "");
141 pwdf = fopen(GUI_RPC_PASSWD_FILE, "r");
142 if (pwdf) {
143 if (fgets(password, 256, pwdf)) {
144 strip_whitespace(password);
145 }
146 fclose(pwdf);
147 }
148 fprintf(f," <gui_rpc_password>%s</gui_rpc_password>\n", password);
149 }
150 }
151 for (i=0; i<gstate.projects.size(); i++) {
152 PROJECT* p = gstate.projects[i];
153 double not_started_dur, in_progress_dur;
154 p->get_task_durs(not_started_dur, in_progress_dur);
155 fprintf(f,
156 " <project>\n"
157 " <url>%s</url>\n"
158 " <project_name>%s</project_name>\n"
159 " <suspended_via_gui>%d</suspended_via_gui>\n"
160 " <hostid>%d</hostid>\n"
161 " <not_started_dur>%f</not_started_dur>\n"
162 " <in_progress_dur>%f</in_progress_dur>\n"
163 " <attached_via_acct_mgr>%d</attached_via_acct_mgr>\n"
164 " <dont_request_more_work>%d</dont_request_more_work>\n"
165 " <detach_when_done>%d</detach_when_done>\n"
166 " <ended>%d</ended>\n"
167 " <resource_share>%f</resource_share>\n"
168 " <cpu_ec>%f</cpu_ec>\n"
169 " <cpu_time>%f</cpu_time>\n"
170 " <gpu_ec>%f</gpu_ec>\n"
171 " <gpu_time>%f</gpu_time>\n"
172 " <njobs_success>%d</njobs_success>\n"
173 " <njobs_error>%d</njobs_error>\n"
174 " <disk_usage>%f</disk_usage>\n"
175 " <disk_share>%f</disk_share>\n",
176 p->master_url,
177 p->project_name,
178 p->suspended_via_gui?1:0,
179 p->hostid,
180 not_started_dur,
181 in_progress_dur,
182 p->attached_via_acct_mgr?1:0,
183 p->dont_request_more_work?1:0,
184 p->detach_when_done?1:0,
185 p->ended?1:0,
186 p->resource_share,
187 p->cpu_ec,
188 p->cpu_time,
189 p->gpu_ec,
190 p->gpu_time,
191 p->njobs_success,
192 p->njobs_error,
193 p->disk_usage,
194 p->disk_share
195 );
196 if (p->attached_via_acct_mgr) {
197 fprintf(f,
198 " <account_key>%s</account_key>\n",
199 p->authenticator
200 );
201 }
202 fprintf(f,
203 " </project>\n"
204 );
205 }
206 MIOFILE mf;
207 mf.init_file(f);
208
209 // send working prefs
210 //
211 fprintf(f, "<working_global_preferences>\n");
212 gstate.global_prefs.write(mf);
213 fprintf(f, "</working_global_preferences>\n");
214
215 if (boinc_file_exists(GLOBAL_PREFS_FILE_NAME)) {
216 FILE* fprefs = fopen(GLOBAL_PREFS_FILE_NAME, "r");
217 if (fprefs) {
218 copy_stream(fprefs, f);
219 fclose(fprefs);
220 }
221 }
222 gstate.host_info.write(mf, !cc_config.suppress_net_info, true);
223 if (strlen(gstate.acct_mgr_info.opaque)) {
224 fprintf(f,
225 " <opaque>\n%s\n"
226 " </opaque>\n",
227 gstate.acct_mgr_info.opaque
228 );
229 }
230 gstate.time_stats.write(mf, true);
231 gstate.net_stats.write(mf);
232 fprintf(f, "</acct_mgr_request>\n");
233 fclose(f);
234 snprintf(buf, sizeof(buf), "%srpc.php", url);
235 retval = gui_http->do_rpc_post(
236 this, buf, ACCT_MGR_REQUEST_FILENAME, ACCT_MGR_REPLY_FILENAME, true
237 );
238 if (retval) {
239 error_num = retval;
240 return retval;
241 }
242 msg_printf(NULL, MSG_INFO, "Contacting account manager at %s", url);
243
244 return 0;
245 }
246
handle_no_rsc(const char * name,bool value)247 void AM_ACCOUNT::handle_no_rsc(const char* name, bool value) {
248 int i = rsc_index(name);
249 if (i < 0) return;
250 no_rsc[i] = value;
251 }
252
parse(XML_PARSER & xp)253 int AM_ACCOUNT::parse(XML_PARSER& xp) {
254 char buf[256];
255 bool btemp;
256 int retval;
257 double dtemp;
258
259 detach = false;
260 update = false;
261 memset(no_rsc, 0, sizeof(no_rsc));
262 dont_request_more_work.init();
263 detach_when_done.init();
264 suspend.init();
265 abort_not_started.init();
266 url = "";
267 safe_strcpy(url_signature, "");
268 authenticator = "";
269 resource_share.init();
270
271 while (!xp.get_tag()) {
272 if (!xp.is_tag) {
273 if (log_flags.unparsed_xml) {
274 msg_printf(0, MSG_INFO,
275 "[unparsed_xml] AM_ACCOUNT::parse: unexpected text %s",
276 xp.parsed_tag
277 );
278 }
279 continue;
280 }
281 if (xp.match_tag("/account")) {
282 if (url.length()) return 0;
283 return ERR_XML_PARSE;
284 }
285 if (xp.parse_string("url", url)) continue;
286 if (xp.match_tag("url_signature")) {
287 retval = xp.element_contents("</url_signature>", url_signature, sizeof(url_signature));
288 if (retval) return retval;
289 safe_strcat(url_signature, "\n");
290 continue;
291 }
292 if (xp.parse_string("authenticator", authenticator)) continue;
293 if (xp.parse_bool("detach", detach)) continue;
294 if (xp.parse_bool("update", update)) continue;
295 if (xp.parse_bool("no_cpu", btemp)) {
296 handle_no_rsc("CPU", btemp);
297 continue;
298 }
299
300 // deprecated
301 if (xp.parse_bool("no_cuda", btemp)) {
302 handle_no_rsc(GPU_TYPE_NVIDIA, btemp);
303 continue;
304 }
305 if (xp.parse_bool("no_ati", btemp)) {
306 handle_no_rsc(GPU_TYPE_NVIDIA, btemp);
307 continue;
308 }
309
310 if (xp.parse_str("no_rsc", buf, sizeof(buf))) {
311 handle_no_rsc(buf, true);
312 continue;
313 }
314 if (xp.parse_bool("dont_request_more_work", btemp)) {
315 dont_request_more_work.set(btemp);
316 continue;
317 }
318 if (xp.parse_bool("detach_when_done", btemp)) {
319 detach_when_done.set(btemp);
320 continue;
321 }
322 if (xp.parse_double("resource_share", dtemp)) {
323 if (dtemp >= 0) {
324 resource_share.set(dtemp);
325 } else {
326 msg_printf(NULL, MSG_INFO,
327 "Resource share out of range: %f", dtemp
328 );
329 }
330 continue;
331 }
332 if (xp.parse_bool("suspend", btemp)) {
333 suspend.set(btemp);
334 continue;
335 }
336 if (xp.parse_bool("abort_not_started", btemp)) {
337 abort_not_started.set(btemp);
338 continue;
339 }
340 if (xp.parse_string("sci_keywords", sci_keywords)) {
341 continue;
342 }
343 if (xp.parse_string("loc_keywords", loc_keywords)) {
344 continue;
345 }
346 if (log_flags.unparsed_xml) {
347 msg_printf(NULL, MSG_INFO,
348 "[unparsed_xml] AM_ACCOUNT: unrecognized %s", xp.parsed_tag
349 );
350 }
351 xp.skip_unexpected(log_flags.unparsed_xml, "AM_ACCOUNT::parse");
352 }
353 return ERR_XML_PARSE;
354 }
355
356 // parse RPC reply from account manager
357 //
parse(FILE * f)358 int ACCT_MGR_OP::parse(FILE* f) {
359 string message;
360 int retval;
361 MIOFILE mf;
362 mf.init_file(f);
363 XML_PARSER xp(&mf);
364
365 accounts.clear();
366 error_str = "";
367 error_num = 0;
368 repeat_sec = 0;
369 safe_strcpy(host_venue, "");
370 safe_strcpy(ami.opaque, "");
371 ami.no_project_notices = false;
372 rss_feeds.clear();
373 if (!xp.parse_start("acct_mgr_reply")) return ERR_XML_PARSE;
374 while (!xp.get_tag()) {
375 if (!xp.is_tag) {
376 if (log_flags.unparsed_xml) {
377 msg_printf(0, MSG_INFO,
378 "[unparsed_xml] ACCT_MGR_OP::parse: unexpected text %s",
379 xp.parsed_tag
380 );
381 }
382 continue;
383 }
384 if (xp.match_tag("/acct_mgr_reply")) return 0;
385 if (xp.parse_str("name", ami.project_name, 256)) continue;
386 if (xp.parse_int("error_num", error_num)) continue;
387 if (xp.parse_string("error", error_str)) continue;
388 if (xp.parse_string("error_msg", error_str)) continue;
389 if (xp.parse_double("repeat_sec", repeat_sec)) continue;
390 if (xp.parse_string("message", message)) {
391 msg_printf(NULL, MSG_INFO, "Account manager: %s", message.c_str());
392 continue;
393 }
394 if (xp.match_tag("opaque")) {
395 retval = xp.element_contents("</opaque>", ami.opaque, sizeof(ami.opaque));
396 if (retval) return retval;
397 continue;
398 }
399 if (xp.match_tag("signing_key")) {
400 retval = xp.element_contents("</signing_key>", ami.signing_key, sizeof(ami.signing_key));
401 if (retval) return retval;
402 continue;
403 }
404 if (xp.match_tag("account")) {
405 AM_ACCOUNT account;
406 retval = account.parse(xp);
407 if (retval) {
408 msg_printf(NULL, MSG_INTERNAL_ERROR,
409 "Can't parse account in account manager reply: %s",
410 boincerror(retval)
411 );
412 } else {
413 accounts.push_back(account);
414 }
415 continue;
416 }
417 if (xp.match_tag("global_preferences")) {
418 retval = dup_element_contents(
419 f,
420 "</global_preferences>",
421 &global_prefs_xml
422 );
423 if (retval) {
424 msg_printf(NULL, MSG_INTERNAL_ERROR,
425 "Can't parse global prefs in account manager reply: %s",
426 boincerror(retval)
427 );
428 return retval;
429 }
430 continue;
431 }
432 if (xp.parse_str("host_venue", host_venue, sizeof(host_venue))) {
433 continue;
434 }
435 if (xp.match_tag("rss_feeds")) {
436 got_rss_feeds = true;
437 parse_rss_feed_descs(xp, rss_feeds);
438 continue;
439 }
440 if (xp.parse_bool("no_project_notices", ami.no_project_notices)) {
441 continue;
442 }
443 if (log_flags.unparsed_xml) {
444 msg_printf(NULL, MSG_INFO,
445 "[unparsed_xml] ACCT_MGR_OP::parse: unrecognized tag <%s>",
446 xp.parsed_tag
447 );
448 }
449 xp.skip_unexpected(log_flags.unparsed_xml, "ACCT_MGR_OP::parse");
450 }
451 return ERR_XML_PARSE;
452 }
453
is_weak_auth(const char * auth)454 static inline bool is_weak_auth(const char* auth) {
455 return (strstr(auth, "_") != NULL);
456 }
457
handle_reply(int http_op_retval)458 void ACCT_MGR_OP::handle_reply(int http_op_retval) {
459 #ifndef SIM
460 unsigned int i;
461 int retval;
462 bool verified;
463 PROJECT* pp;
464 bool sig_ok;
465 bool got_error = false;
466
467 // check for failures of HTTP OP, reply parse
468 //
469 if (http_op_retval) {
470 msg_printf(&ami, MSG_INFO, "AM RPC HTTP failure: %s",
471 boincerror(http_op_retval)
472 );
473 got_error = true;
474 } else {
475 FILE* f = fopen(ACCT_MGR_REPLY_FILENAME, "r");
476 if (f) {
477 retval = parse(f);
478 if (retval) {
479 got_error = true;
480 msg_printf(&ami, MSG_INFO, "AM reply parse error");
481 }
482 fclose(f);
483 } else {
484 msg_printf(&ami, MSG_INFO, "AM reply file missing");
485 got_error = true;
486 }
487 }
488
489 // if no errors so far, check for errors from AM
490 //
491 if (!got_error) {
492 gstate.acct_mgr_info.password_error = false;
493 if (error_num == ERR_BAD_PASSWD && !via_gui) {
494 gstate.acct_mgr_info.password_error = true;
495 }
496
497 // Show error message from AM if available.
498 // check both error_str and error_num since an account manager may only
499 // return a BOINC based error code for password failures or invalid
500 // email addresses
501 //
502 if (error_str.size()) {
503 msg_printf(&ami, MSG_USER_ALERT,
504 "%s: %s",
505 _("Message from account manager"),
506 error_str.c_str()
507 );
508 got_error = true;
509 } else if (error_num) {
510 msg_printf(&ami, MSG_USER_ALERT,
511 "%s: %s",
512 _("Message from account manager"),
513 boincerror(error_num)
514 );
515 got_error = true;
516 }
517 }
518
519 if (got_error) {
520 gstate.acct_mgr_info.next_rpc_time =
521 gstate.now
522 + calculate_exponential_backoff(
523 gstate.acct_mgr_info.nfailures,
524 ACCT_MGR_MIN_BACKOFF, ACCT_MGR_MAX_BACKOFF
525 )
526 ;
527 gstate.acct_mgr_info.nfailures++;
528 return;
529 }
530
531 // The RPC was successful
532 //
533 // Detach projects that are
534 // - detach_when_done
535 // - done
536 // - attached via AM
537 //
538 while (1) {
539 bool found = false;
540 for (i=0; i<gstate.projects.size(); i++) {
541 PROJECT* p = gstate.projects[i];
542 if (p->detach_when_done && !gstate.nresults_for_project(p) && p->attached_via_acct_mgr) {
543 gstate.detach_project(p);
544 found = true;
545 }
546 }
547 if (!found) break;
548 }
549
550 gstate.acct_mgr_info.nfailures = 0;
551
552 msg_printf(NULL, MSG_INFO, "Account manager contact succeeded");
553
554 // demand a signing key
555 //
556 sig_ok = true;
557 if (!strlen(ami.signing_key)) {
558 msg_printf(NULL, MSG_INTERNAL_ERROR,
559 "No signing key from account manager"
560 );
561 sig_ok = false;
562 }
563
564 // don't accept new signing key if we already have one
565 //
566 if (strlen(gstate.acct_mgr_info.signing_key)
567 && strcmp(gstate.acct_mgr_info.signing_key, ami.signing_key)
568 ) {
569 msg_printf(NULL, MSG_INTERNAL_ERROR,
570 "Inconsistent signing key from account manager"
571 );
572 sig_ok = false;
573 }
574
575 if (sig_ok) {
576 // if the AM RPC had an error, some items may be missing; don't copy
577 //
578 if (strlen(ami.project_name)) {
579 safe_strcpy(gstate.acct_mgr_info.project_name, ami.project_name);
580 }
581 if (strlen(ami.signing_key)) {
582 safe_strcpy(gstate.acct_mgr_info.signing_key, ami.signing_key);
583 }
584 if (strlen(ami.opaque)) {
585 safe_strcpy(gstate.acct_mgr_info.opaque, ami.opaque);
586 }
587 safe_strcpy(gstate.acct_mgr_info.master_url, ami.master_url);
588 safe_strcpy(gstate.acct_mgr_info.login_name, ami.login_name);
589 safe_strcpy(gstate.acct_mgr_info.password_hash, ami.password_hash);
590 gstate.acct_mgr_info.no_project_notices = ami.no_project_notices;
591
592 // process projects
593 //
594 for (i=0; i<accounts.size(); i++) {
595 AM_ACCOUNT& acct = accounts[i];
596 pp = gstate.lookup_project(acct.url.c_str());
597 if (pp) {
598 if (acct.detach) {
599 if (pp->attached_via_acct_mgr) {
600 gstate.detach_project(pp);
601 }
602 } else {
603 // The AM can leave authenticator blank if request message
604 // had the current account info
605 //
606 if (acct.authenticator.size()) {
607 if (strcmp(pp->authenticator, acct.authenticator.c_str())) {
608 // if old and new auths are both weak,
609 // use the new one
610 //
611 if (is_weak_auth(pp->authenticator)
612 && is_weak_auth(acct.authenticator.c_str())
613 ) {
614 safe_strcpy(pp->authenticator, acct.authenticator.c_str());
615 msg_printf(pp, MSG_INFO,
616 "Received new authenticator from account manager"
617 );
618 } else {
619 // otherwise skip this update
620 //
621 msg_printf(pp, MSG_INFO,
622 "Already attached to a different account"
623 );
624 continue;
625 }
626 }
627 }
628 pp->attached_via_acct_mgr = true;
629 if (acct.dont_request_more_work.present) {
630 pp->dont_request_more_work = acct.dont_request_more_work.value;
631 } else {
632 pp->dont_request_more_work = false;
633 }
634 if (acct.detach_when_done.present) {
635 pp->detach_when_done = acct.detach_when_done.value;
636 if (pp->detach_when_done) {
637 pp->dont_request_more_work = true;
638 }
639 } else {
640 pp->detach_when_done = false;
641 }
642
643 // initiate a scheduler RPC if requested by AMS
644 //
645 if (acct.update) {
646 pp->sched_rpc_pending = RPC_REASON_ACCT_MGR_REQ;
647 pp->min_rpc_time = 0;
648 }
649 if (acct.resource_share.present) {
650 pp->ams_resource_share = acct.resource_share.value;
651 pp->resource_share = pp->ams_resource_share;
652 } else {
653 // no host-specific resource share;
654 // if currently have one, restore to value from web
655 //
656 if (pp->ams_resource_share >= 0) {
657 pp->ams_resource_share = -1;
658 PROJECT p2;
659 safe_strcpy(p2.master_url, pp->master_url);
660 retval = p2.parse_account_file();
661 if (!retval) {
662 pp->resource_share = p2.resource_share;
663 } else {
664 pp->resource_share = 100;
665 }
666 }
667 }
668
669 if (acct.suspend.present) {
670 if (acct.suspend.value) {
671 pp->suspend();
672 } else {
673 pp->resume();
674 }
675 }
676 if (acct.abort_not_started.present) {
677 if (acct.abort_not_started.value) {
678 pp->abort_not_started();
679 }
680 }
681 for (int j=0; j<MAX_RSC; j++) {
682 pp->no_rsc_ams[j] = acct.no_rsc[j];
683 }
684 pp->sci_keywords = acct.sci_keywords;
685 pp->loc_keywords = acct.loc_keywords;
686 }
687 } else {
688 // here we don't already have the project.
689 //
690 retval = check_string_signature2(
691 acct.url.c_str(), acct.url_signature, ami.signing_key, verified
692 );
693 if (retval || !verified) {
694 msg_printf(NULL, MSG_INTERNAL_ERROR,
695 "Bad signature for URL %s", acct.url.c_str()
696 );
697 continue;
698 }
699 if (acct.authenticator.empty()) {
700 msg_printf(NULL, MSG_INFO,
701 "Account manager reply missing authenticator for %s",
702 acct.url.c_str()
703 );
704 continue;
705 }
706
707 // Attach to it, unless the acct mgr is telling us to detach
708 //
709 if (!acct.detach && !(acct.detach_when_done.present && acct.detach_when_done.value)) {
710 msg_printf(NULL, MSG_INFO,
711 "Attaching to %s", acct.url.c_str()
712 );
713 gstate.add_project(
714 acct.url.c_str(), acct.authenticator.c_str(), "", true
715 );
716 pp = gstate.lookup_project(acct.url.c_str());
717 if (pp) {
718 for (int j=0; j<MAX_RSC; j++) {
719 pp->no_rsc_ams[j] = acct.no_rsc[j];
720 }
721 if (acct.dont_request_more_work.present) {
722 pp->dont_request_more_work = acct.dont_request_more_work.value;
723 }
724 if (acct.suspend.present && acct.suspend.value) {
725 pp->suspend();
726 }
727 } else {
728 msg_printf(NULL, MSG_INTERNAL_ERROR,
729 "Failed to add project: %s",
730 acct.url.c_str()
731 );
732 }
733 }
734 }
735 }
736
737 #ifdef USE_NET_PREFS
738 bool read_prefs = false;
739 if (strlen(host_venue) && strcmp(host_venue, gstate.main_host_venue)) {
740 safe_strcpy(gstate.main_host_venue, host_venue);
741 read_prefs = true;
742 }
743
744 // process prefs if any
745 //
746 if (global_prefs_xml) {
747 retval = gstate.save_global_prefs(
748 global_prefs_xml, ami.master_url, ami.master_url
749 );
750 if (retval) {
751 msg_printf(NULL, MSG_INTERNAL_ERROR, "Can't save global prefs");
752 }
753 read_prefs = true;
754 }
755
756 // process prefs if prefs or venue changed
757 //
758 if (read_prefs) {
759 gstate.read_global_prefs();
760 }
761 #endif
762
763 handle_sr_feeds(rss_feeds, &gstate.acct_mgr_info);
764
765 // in case no_project_notices changed
766 //
767 ::rss_feeds.update_feed_list();
768 }
769
770 safe_strcpy(
771 gstate.acct_mgr_info.previous_host_cpid, gstate.host_info.host_cpid
772 );
773 if (repeat_sec) {
774 gstate.acct_mgr_info.next_rpc_time = gstate.now + repeat_sec;
775 } else {
776 gstate.acct_mgr_info.next_rpc_time = gstate.now + 86400;
777 }
778 gstate.acct_mgr_info.write_info();
779 gstate.set_client_state_dirty("account manager RPC");
780 #endif
781 }
782
783 // write AM info to files.
784 // This is done after each AM RPC,
785 // perhaps overkill since the info doesn't generally change.
786 // But doesn't matter since infrequent.
787 //
write_info()788 int ACCT_MGR_INFO::write_info() {
789 FILE* f;
790 if (strlen(master_url)) {
791 f = fopen(ACCT_MGR_URL_FILENAME, "w");
792 if (!f) {
793 msg_printf(NULL, MSG_USER_ALERT,
794 "Can't write to %s; check file and directory permissions",
795 ACCT_MGR_URL_FILENAME
796 );
797 return ERR_FOPEN;
798 }
799 fprintf(f,
800 "<acct_mgr>\n"
801 " <name>%s</name>\n"
802 " <url>%s</url>\n",
803 project_name,
804 master_url
805 );
806 if (send_gui_rpc_info) {
807 fprintf(f, " <send_gui_rpc_info/>\n");
808 }
809 if (strlen(signing_key)) {
810 fprintf(f,
811 " <signing_key>\n%s\n</signing_key>\n",
812 signing_key
813 );
814 }
815 fprintf(f,
816 "</acct_mgr>\n"
817 );
818 fclose(f);
819 }
820
821 if (strlen(login_name)) {
822 f = fopen(ACCT_MGR_LOGIN_FILENAME, "w");
823 if (!f) {
824 msg_printf(NULL, MSG_USER_ALERT,
825 "Can't write to %s; check file and directory permissions",
826 ACCT_MGR_LOGIN_FILENAME
827 );
828 return ERR_FOPEN;
829 }
830 fprintf(f,
831 "<acct_mgr_login>\n"
832 " <login>%s</login>\n"
833 " <password_hash>%s</password_hash>\n"
834 " <previous_host_cpid>%s</previous_host_cpid>\n"
835 " <next_rpc_time>%f</next_rpc_time>\n"
836 " <opaque>\n%s\n"
837 " </opaque>\n"
838 " <no_project_notices>%d</no_project_notices>\n",
839 login_name,
840 password_hash,
841 previous_host_cpid,
842 next_rpc_time,
843 opaque,
844 no_project_notices?1:0
845 );
846 if (!sched_req_opaque.empty()) {
847 fprintf(f,
848 "<sched_req_opaque>\n<![CDATA[\n%s\n]]>\n</sched_req_opaque>\n",
849 sched_req_opaque.c_str()
850 );
851 }
852 fprintf(f,
853 "</acct_mgr_login>\n"
854 );
855 fclose(f);
856 }
857 return 0;
858 }
859
clear()860 void ACCT_MGR_INFO::clear() {
861 safe_strcpy(project_name, "");
862 safe_strcpy(master_url, "");
863 safe_strcpy(login_name, "");
864 safe_strcpy(user_name, "");
865 safe_strcpy(password_hash, "");
866 safe_strcpy(signing_key, "");
867 safe_strcpy(previous_host_cpid, "");
868 safe_strcpy(opaque, "");
869 sched_req_opaque.clear();
870 safe_strcpy(cookie_failure_url, "");
871 next_rpc_time = 0;
872 nfailures = 0;
873 send_gui_rpc_info = false;
874 password_error = false;
875 no_project_notices = false;
876 cookie_required = false;
877 }
878
ACCT_MGR_INFO()879 ACCT_MGR_INFO::ACCT_MGR_INFO() {
880 clear();
881 }
882
parse_login_file(FILE * p)883 int ACCT_MGR_INFO::parse_login_file(FILE* p) {
884 MIOFILE mf;
885 int retval;
886
887 mf.init_file(p);
888 XML_PARSER xp(&mf);
889 if (!xp.parse_start("acct_mgr_login")) {
890 msg_printf(NULL, MSG_INTERNAL_ERROR,
891 "missing start tag in account manager login file"
892 );
893 }
894 while (!xp.get_tag()) {
895 if (!xp.is_tag) {
896 printf("unexpected text: %s\n", xp.parsed_tag);
897 continue;
898 }
899 if (xp.match_tag("/acct_mgr_login")) break;
900 else if (xp.parse_str("login", login_name, 256)) continue;
901 else if (xp.parse_str("password_hash", password_hash, 256)) continue;
902 else if (xp.parse_str("previous_host_cpid", previous_host_cpid, sizeof(previous_host_cpid))) continue;
903 else if (xp.parse_double("next_rpc_time", next_rpc_time)) continue;
904 else if (xp.match_tag("opaque")) {
905 retval = xp.element_contents("</opaque>", opaque, sizeof(opaque));
906 if (retval) {
907 msg_printf(NULL, MSG_INFO,
908 "error parsing <opaque> in acct_mgr_login.xml"
909 );
910 }
911 continue;
912 }
913 else if (xp.match_tag("sched_req_opaque")) {
914 char buf[65536];
915 retval = xp.element_contents(
916 "</sched_req_opaque>", buf, sizeof(buf)
917 );
918 if (retval) {
919 msg_printf(NULL, MSG_INFO,
920 "error parsing <sched_req_opaque> in acct_mgr_login.xml"
921 );
922 }
923 sched_req_opaque = string(buf);
924 continue;
925 }
926 else if (xp.parse_bool("no_project_notices", no_project_notices)) continue;
927 if (log_flags.unparsed_xml) {
928 msg_printf(NULL, MSG_INFO,
929 "[unparsed_xml] unrecognized %s in acct_mgr_login.xml",
930 xp.parsed_tag
931 );
932 }
933 xp.skip_unexpected(
934 log_flags.unparsed_xml, "ACCT_MGR_INFO::parse_login_file"
935 );
936 }
937 return 0;
938 }
939
940 // called at client startup.
941 // If currently using an AM, read its URL and login files
942 //
init()943 int ACCT_MGR_INFO::init() {
944 MIOFILE mf;
945 FILE* p;
946 int retval;
947
948 clear();
949 p = fopen(ACCT_MGR_URL_FILENAME, "r");
950 if (!p) {
951 // if not using acct mgr, make sure projects not flagged,
952 // otherwise won't be able to detach them.
953 //
954 for (unsigned int i=0; i<gstate.projects.size(); i++) {
955 gstate.projects[i]->attached_via_acct_mgr = false;
956 }
957 return 0;
958 }
959 mf.init_file(p);
960 XML_PARSER xp(&mf);
961 if (!xp.parse_start("acct_mgr")) {
962 //
963 }
964 while (!xp.get_tag()) {
965 if (!xp.is_tag) {
966 printf("unexpected text: %s\n", xp.parsed_tag);
967 continue;
968 }
969 if (xp.match_tag("/acct_mgr")) break;
970 else if (xp.parse_str("name", project_name, 256)) continue;
971 else if (xp.parse_str("url", master_url, 256)) continue;
972 else if (xp.parse_bool("send_gui_rpc_info", send_gui_rpc_info)) continue;
973 else if (xp.match_tag("signing_key")) {
974 retval = xp.element_contents("</signing_key>", signing_key, sizeof(signing_key));
975 if (retval) {
976 msg_printf(NULL, MSG_INFO,
977 "error parsing <signing_key> in acct_mgr_url.xml"
978 );
979 }
980 continue;
981 }
982 else if (xp.parse_bool("cookie_required", cookie_required)) continue;
983 else if (xp.parse_str("cookie_failure_url", cookie_failure_url, 256)) continue;
984 if (log_flags.unparsed_xml) {
985 msg_printf(NULL, MSG_INFO,
986 "[unparsed_xml] ACCT_MGR_INFO::init: unrecognized %s",
987 xp.parsed_tag
988 );
989 }
990 xp.skip_unexpected(log_flags.unparsed_xml, "ACCT_MGR_INFO::init");
991 }
992 fclose(p);
993
994 p = fopen(ACCT_MGR_LOGIN_FILENAME, "r");
995 if (p) {
996 parse_login_file(p);
997 fclose(p);
998 }
999 if (using_am()) {
1000 msg_printf(NULL, MSG_INFO, "Using account manager %s", project_name);
1001 if (strlen(user_name)) {
1002 msg_printf(NULL, MSG_INFO, "Account manager login: %s", user_name);
1003 }
1004 }
1005 return 0;
1006 }
1007
poll()1008 bool ACCT_MGR_INFO::poll() {
1009 if (!using_am()) return false;
1010 if (gstate.acct_mgr_op.gui_http->is_busy()) return false;
1011
1012 if (gstate.now > next_rpc_time) {
1013
1014 // default synch period is 1 day
1015 //
1016 next_rpc_time = gstate.now + 86400;
1017 gstate.acct_mgr_op.do_rpc(
1018 master_url, login_name, password_hash, false
1019 );
1020 return true;
1021 }
1022 return false;
1023 }
1024