1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2008 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 // Handle a scheduling server RPC
19 
20 #include "config.h"
21 #ifdef _USING_FCGI_
22 #include "boinc_fcgi.h"
23 #else
24 #include <cstdio>
25 #endif
26 #include <cassert>
27 #include <cstdlib>
28 #include <vector>
29 #include <string>
30 #include <cstring>
31 #include <ctime>
32 #include <cmath>
33 
34 #include <unistd.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <sys/stat.h>
40 
41 #include "backend_lib.h"
42 #include "boinc_db.h"
43 #include "error_numbers.h"
44 #include "filesys.h"
45 #include "parse.h"
46 #include "str_replace.h"
47 #include "str_util.h"
48 #include "util.h"
49 
50 #include "sched_vda.h"
51 
52 #include "credit.h"
53 #include "sched_files.h"
54 #include "sched_main.h"
55 #include "sched_types.h"
56 #include "sched_util.h"
57 #include "handle_request.h"
58 #include "sched_msgs.h"
59 #include "sched_resend.h"
60 #include "sched_send.h"
61 #include "sched_config.h"
62 #include "sched_locality.h"
63 #include "sched_result.h"
64 #include "sched_customize.h"
65 #include "time_stats_log.h"
66 
67 // are the 2 hosts obviously different computers?
68 //
obviously_different(HOST & h1,HOST & h2)69 static bool obviously_different(HOST& h1, HOST& h2) {
70     if (h1.p_ncpus != h2.p_ncpus) return true;
71     if (strcmp(h1.p_vendor, h2.p_vendor)) return true;
72     if (strcmp(h1.p_model, h2.p_model)) return true;
73     if (strcmp(h1.os_name, h2.os_name)) return true;
74     if (strcmp(h1.os_version, h2.os_version)) return true;
75     return false;
76 }
77 
78 // find the user's most recently-created host with given various characteristics
79 //
find_host_by_other(DB_USER & user,HOST req_host,DB_HOST & host)80 static bool find_host_by_other(DB_USER& user, HOST req_host, DB_HOST& host) {
81     char buf[2048];
82     char dn[512], ip[512], os[512], pm[512];
83 
84     // don't dig through hosts of these users
85     // prevents flooding the DB with slow queries from users with many hosts
86     //
87     for (unsigned int i=0; i < config.dont_search_host_for_userid.size(); i++) {
88         if (user.id == config.dont_search_host_for_userid[i]) {
89             return false;
90         }
91     }
92 
93     // Only check if all the fields are populated
94     //
95     if (strlen(req_host.domain_name) && strlen(req_host.last_ip_addr) && strlen(req_host.os_name) && strlen(req_host.p_model)) {
96         safe_strcpy(dn, req_host.domain_name);
97         escape_string(dn, sizeof(dn));
98         safe_strcpy(ip, req_host.last_ip_addr);
99         escape_string(ip, sizeof(ip));
100         safe_strcpy(os, req_host.os_name);
101         escape_string(os, sizeof(os));
102         safe_strcpy(pm, req_host.p_model);
103         escape_string(pm, sizeof(pm));
104 
105         sprintf(buf,
106             "where userid=%lu and id>%lu and domain_name='%s' and last_ip_addr = '%s' and os_name = '%s' and p_model = '%s'"
107                " and m_nbytes = %lf order by id desc", user.id, req_host.id, dn, ip, os, pm, req_host.m_nbytes
108         );
109         if (!host.enumerate(buf)) {
110             host.end_enumerate();
111             return true;
112         }
113     }
114     return false;
115 }
116 
send_error_message(const char * msg,int delay)117 static void send_error_message(const char* msg, int delay) {
118     g_reply->insert_message(msg, "low");
119     g_reply->set_delay(delay);
120     g_reply->nucleus_only = true;
121 }
122 
123 // Try to lock a file with name based on host ID,
124 // to prevent 2 schedulers from running at same time for same host.
125 // Return:
126 // 0 if successful
127 //    In this case store file descriptor in reply struct so we can unlock later
128 //    In other cases store -1 in reply struct
129 // PID (>0) if another process has lock
130 // -1 if error (e.g. can't create file)
131 //
lock_sched()132 int lock_sched() {
133     char filename[256];
134     char pid_string[16];
135     int fd, pid, count;
136 
137     g_reply->lockfile_fd=-1;
138 
139     sprintf(filename, "%s/CGI_%07lu",
140         config.sched_lockfile_dir, g_reply->host.id
141     );
142 
143     fd = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
144     if (fd < 0) return -1;
145 
146     // if we can't get an advisory write lock on the file,
147     // return the PID of the process that DOES hold the lock.
148     // (or -1 if failure)
149     //
150     pid = mylockf(fd);
151     if (pid) {
152         close(fd);
153         return pid;
154     }
155 
156     // write PID into the CGI_<HOSTID> file and flush to disk
157     //
158     count = sprintf(pid_string, "%d\n", getpid());
159     ssize_t n = write(fd, pid_string, count);
160     if (n < 0) {
161         close(fd);
162         return -1;
163     }
164     fsync(fd);
165 
166     g_reply->lockfile_fd = fd;
167     return 0;
168 }
169 
170 // unlock and delete per-host lockfile
171 //
unlock_sched()172 void unlock_sched() {
173     char filename[256];
174 
175     if (g_reply->lockfile_fd < 0) return;
176     sprintf(filename, "%s/CGI_%07lu", config.sched_lockfile_dir, g_reply->host.id);
177     unlink(filename);
178     close(g_reply->lockfile_fd);
179 }
180 
181 
182 // find the user's most recently-created host with given host CPID
183 //
find_host_by_cpid(DB_USER & user,char * host_cpid,DB_HOST & host)184 static bool find_host_by_cpid(DB_USER& user, char* host_cpid, DB_HOST& host) {
185     char buf[1024], buf2[256];
186     sprintf(buf, "%s%s", host_cpid, user.email_addr);
187     md5_block((const unsigned char*)buf, strlen(buf), buf2);
188 
189     sprintf(buf,
190         "where userid=%lu and host_cpid='%s' order by id desc", user.id, buf2
191     );
192     if (!host.enumerate(buf)) {
193         host.end_enumerate();
194         return true;
195     }
196     return false;
197 }
198 
199 // Called when there's evidence that the host has detached.
200 // Mark in-progress results for the given host
201 // as server state OVER, outcome CLIENT_DETACHED.
202 // This serves two purposes:
203 // 1) make sure we don't resend these results to the host
204 //    (they may be the reason the user detached)
205 // 2) trigger the generation of new results for these WUs
206 //
mark_results_over(DB_HOST & host)207 static void mark_results_over(DB_HOST& host) {
208     char buf[256], buf2[256];
209     DB_RESULT result;
210     sprintf(buf, "where hostid=%lu and server_state=%d",
211         host.id,
212         RESULT_SERVER_STATE_IN_PROGRESS
213     );
214     while (!result.enumerate(buf)) {
215         sprintf(buf2,
216             "server_state=%d, outcome=%d, received_time = %ld",
217             RESULT_SERVER_STATE_OVER,
218             RESULT_OUTCOME_CLIENT_DETACHED,
219             time(0)
220         );
221         result.update_field(buf2);
222 
223         // and trigger WU transition
224         //
225         DB_WORKUNIT wu;
226         wu.id = result.workunitid;
227         sprintf(buf2, "transition_time=%d", (int)time(0));
228         wu.update_field(buf2);
229 
230         log_messages.printf(MSG_CRITICAL,
231             "[HOST#%lu] [RESULT#%lu] [WU#%lu] changed CPID: marking in-progress result %s as client error!\n",
232             host.id, result.id, result.workunitid, result.name
233         );
234     }
235 }
236 
237 // Based on the info in the request message,
238 // look up the host and its user, and make sure the authenticator matches.
239 // Some special cases:
240 //  1) If no host ID is supplied, or if RPC seqno mismatch,
241 //     create a new host record
242 //  2) If the host record specified by g_request->hostid is a "zombie"
243 //     (i.e. it was merged with another host via the web site)
244 //     then follow links to find the proper host
245 //
246 // POSTCONDITION:
247 // If this function returns zero, then:
248 // - reply.host contains a valid host record (possibly new)
249 // - reply.user contains a valid user record
250 // - if user belongs to a team, reply.team contains team record
251 //
authenticate_user()252 int authenticate_user() {
253     int retval;
254     char buf[1024];
255     DB_HOST host;
256     DB_USER user;
257     DB_TEAM team;
258 
259     if (g_request->hostid) {
260         retval = host.lookup_id(g_request->hostid);
261         while (!retval && host.userid==0) {
262             // if host record is zombie, follow link to new host
263             //
264             retval = host.lookup_id(host.rpc_seqno);
265             if (!retval) {
266                 g_reply->hostid = host.id;
267                 log_messages.printf(MSG_NORMAL,
268                     "[HOST#%lu] forwarding to new host ID %lu\n",
269                     g_request->hostid, host.id
270                 );
271             }
272         }
273         if (retval) {
274             g_reply->insert_message("Can't find host record", "low");
275             log_messages.printf(MSG_NORMAL,
276                 "[HOST#%lu?] can't find host\n",
277                 g_request->hostid
278             );
279             g_request->hostid = 0;
280             goto lookup_user_and_make_new_host;
281         }
282 
283         g_reply->host = host;
284 
285         // look up user based on the ID in host record,
286         // and see if the authenticator matches (regular or weak)
287         //
288         g_request->using_weak_auth = false;
289         sprintf(buf, "where id=%lu", host.userid);
290         retval = user.lookup(buf);
291         if (!retval && !strcmp(user.authenticator, g_request->authenticator)) {
292             // req auth matches user auth - go on
293         } else {
294             if (!retval) {
295                 // user for host.userid exists - check weak auth
296                 //
297                 get_weak_auth(user, buf);
298                 if (!strcmp(buf, g_request->authenticator)) {
299                     g_request->using_weak_auth = true;
300                     log_messages.printf(MSG_DEBUG,
301                         "[HOST#%lu] accepting weak authenticator\n",
302                         host.id
303                     );
304                 }
305             }
306             if (!g_request->using_weak_auth) {
307                 // weak auth failed - look up user based on authenticator
308                 //
309                 strlcpy(
310                     user.authenticator, g_request->authenticator, sizeof(user.authenticator)
311                 );
312                 escape_string(user.authenticator, sizeof(user.authenticator));
313                 sprintf(buf, "where authenticator='%s'", user.authenticator);
314                 retval = user.lookup(buf);
315                 if (retval) {
316                     g_reply->insert_message(
317                         _("Invalid or missing account key.  To fix, remove and add this project."),
318                         "notice"
319                     );
320                     g_reply->set_delay(DELAY_MISSING_KEY);
321                     g_reply->nucleus_only = true;
322                     log_messages.printf(MSG_CRITICAL,
323                         "[HOST#%lu] [USER#%lu] Bad authenticator '%s'\n",
324                         host.id, user.id, g_request->authenticator
325                     );
326                     return ERR_AUTHENTICATOR;
327                 }
328             }
329         }
330 
331         g_reply->user = user;
332 
333         if (host.userid != user.id) {
334             // If the request's host ID isn't consistent with the authenticator,
335             // create a new host record.
336             //
337             log_messages.printf(MSG_NORMAL,
338                 "[HOST#%lu] [USER#%lu] inconsistent host ID; creating new host\n",
339                 host.id, user.id
340             );
341             goto make_new_host;
342         }
343 
344 
345         // If the seqno from the host is less than what we expect,
346         // the user must have copied the state file to a different host.
347         // Make a new host record.
348         //
349         if (!batch && g_request->rpc_seqno < g_reply->host.rpc_seqno) {
350             g_request->hostid = 0;
351             log_messages.printf(MSG_NORMAL,
352                 "[HOST#%lu] [USER#%lu] RPC seqno %d less than expected %d; creating new host\n",
353                 g_reply->host.id, user.id, g_request->rpc_seqno, g_reply->host.rpc_seqno
354             );
355             goto make_new_host;
356         }
357 
358     } else {
359         // Here no hostid was given, or the ID was bad.
360         // Look up the user, then create a new host record
361         //
362 lookup_user_and_make_new_host:
363         // if authenticator contains _, it's a weak auth
364         //
365         if (strchr(g_request->authenticator, '_')) {
366             int userid = atoi(g_request->authenticator);
367             retval = user.lookup_id(userid);
368             if (!retval) {
369                 get_weak_auth(user, buf);
370                 if (strcmp(buf, g_request->authenticator)) {
371                     retval = ERR_AUTHENTICATOR;
372                 }
373             }
374         } else {
375             strlcpy(
376                 user.authenticator, g_request->authenticator,
377                 sizeof(user.authenticator)
378             );
379             escape_string(user.authenticator, sizeof(user.authenticator));
380             sprintf(buf, "where authenticator='%s'", user.authenticator);
381             retval = user.lookup(buf);
382         }
383         if (retval) {
384             g_reply->insert_message(
385                 "Invalid or missing account key.  To fix, remove and add this project .",
386                 "low"
387             );
388             g_reply->set_delay(DELAY_MISSING_KEY);
389             log_messages.printf(MSG_CRITICAL,
390                 "[HOST#<none>] Bad authenticator '%s': %s\n",
391                 g_request->authenticator, boincerror(retval)
392             );
393             return ERR_AUTHENTICATOR;
394         }
395         g_reply->user = user;
396 
397         // If host CPID is present,
398         // scan backwards through this user's hosts,
399         // looking for one with the same host CPID.
400         // If we find one, it means the user detached and reattached.
401         // Use the existing host record,
402         // and mark in-progress results as over.
403         //
404         if (strlen(g_request->host.host_cpid)) {
405             if (find_host_by_cpid(user, g_request->host.host_cpid, host)) {
406                 log_messages.printf(MSG_NORMAL,
407                     "[HOST#%lu] [USER#%lu] No host ID in request, but host with matching CPID found.\n",
408                     host.id, host.userid
409                 );
410                 if (obviously_different(host, g_request->host)) {
411                     log_messages.printf(MSG_NORMAL,
412                         "[HOST#%lu] [USER#%lu] But that host doesn't match request.\n",
413                         host.id, host.userid
414                     );
415                 } else {
416                     if ((g_request->allow_multiple_clients != 1)
417                         && (g_request->other_results.size() == 0)
418                     ) {
419                         mark_results_over(host);
420                     }
421                     goto got_host;
422                 }
423             }
424         }
425 
426 make_new_host:
427         // One final attempt to locate an existing host record:
428         // scan backwards through this user's hosts,
429         // looking for one with the same host name,
430         // IP address, processor and amount of RAM.
431         // If found, use the existing host record,
432         // and mark in-progress results as over.
433         //
434         // NOTE: If the client was run with --allow_multiple_clients, skip this.
435         //
436         if ((g_request->allow_multiple_clients != 1)
437             && find_host_by_other(user, g_request->host, host)
438         ) {
439             log_messages.printf(MSG_NORMAL,
440                 "[HOST#%lu] [USER#%lu] Found similar existing host for this user - assigned.\n",
441                 host.id, host.userid
442             );
443             if (g_request->other_results.size() == 0) {
444                 // mark host's jobs as abandoned
445                 // if client has no jobs in progress
446                 //
447                 mark_results_over(host);
448             }
449             goto got_host;
450         }
451         // either of the above cases,
452         // or host ID didn't match user ID,
453         // or RPC seqno was too low.
454         //
455         // Create a new host.
456         // g_reply->user is filled in and valid at this point
457         //
458         host = g_request->host;
459         host.id = 0;
460         host.create_time = time(0);
461         host.userid = g_reply->user.id;
462         host.rpc_seqno = 0;
463         host.expavg_time = time(0);
464         safe_strcpy(host.venue, g_reply->user.venue);
465         host.fix_nans();
466         retval = host.insert();
467         if (retval) {
468             g_reply->insert_message(
469                 "Couldn't create host record in database", "low"
470             );
471             boinc_db.print_error("host.insert()");
472             log_messages.printf(MSG_CRITICAL, "host.insert() failed\n");
473             return retval;
474         }
475         host.id = boinc_db.insert_id();
476 
477 got_host:
478         g_reply->host = host;
479         g_reply->hostid = g_reply->host.id;
480         // this tells client to updates its host ID
481         g_request->rpc_seqno = 0;
482             // this value eventually gets written to host DB record;
483             // for new hosts it must be zero.
484             // This kludge forces this.
485     }
486 
487     // have user record in g_reply->user at this point
488     //
489 
490     if (g_reply->user.teamid) {
491         retval = team.lookup_id(g_reply->user.teamid);
492         if (!retval) g_reply->team = team;
493     }
494 
495     // compute email hash
496     //
497     md5_block(
498         (unsigned char*)g_reply->user.email_addr,
499         strlen(g_reply->user.email_addr),
500         g_reply->email_hash
501     );
502 
503     // if new user CPID, update user record
504     //
505     if (!g_request->using_weak_auth && strlen(g_request->cross_project_id)) {
506         if (strcmp(g_request->cross_project_id, g_reply->user.cross_project_id)) {
507             user.id = g_reply->user.id;
508             escape_string(g_request->cross_project_id, sizeof(g_request->cross_project_id));
509             sprintf(buf, "cross_project_id='%s'", g_request->cross_project_id);
510             unescape_string(g_request->cross_project_id, sizeof(g_request->cross_project_id));
511             user.update_field(buf);
512         }
513     }
514 
515     return 0;
516 }
517 
get_remote_addr()518 inline static const char* get_remote_addr() {
519     // Server is behind a load balancer or proxy
520     const char* p = getenv("HTTP_X_FORWARDED_FOR");
521     if (p) {
522         return p;
523     }
524 
525     const char * r = getenv("REMOTE_ADDR");
526     return r ? r : "?.?.?.?";
527 }
528 
529 // modify host struct based on request.
530 // Copy all fields that are determined by the client.
531 //
modify_host_struct(HOST & host)532 static int modify_host_struct(HOST& host) {
533     host.timezone = g_request->host.timezone;
534     strlcpy(host.domain_name, g_request->host.domain_name, sizeof(host.domain_name));
535     char buf[1024], buf2[1024];
536     sprintf(buf, "[BOINC|%d.%d.%d",
537         g_request->core_client_major_version,
538         g_request->core_client_minor_version,
539         g_request->core_client_release
540     );
541     if (strlen(g_request->client_brand)) {
542         strcat(buf, "|");
543         strcat(buf, g_request->client_brand);
544     }
545     strcat(buf, "]");
546     g_request->coprocs.summary_string(buf2, sizeof(buf2));
547     strlcpy(host.serialnum, buf, sizeof(host.serialnum));
548     strlcat(host.serialnum, buf2, sizeof(host.serialnum));
549     if (strlen(g_request->host.virtualbox_version)) {
550         sprintf(buf2, "[vbox|%s|%d|%d]",
551             g_request->host.virtualbox_version,
552             (strstr(g_request->host.p_features, "vmx") || strstr(g_request->host.p_features, "svm"))?1:0,
553             g_request->host.p_vm_extensions_disabled?0:1
554         );
555         strlcat(host.serialnum, buf2, sizeof(host.serialnum));
556     }
557     if (strcmp(host.last_ip_addr, g_request->host.last_ip_addr)) {
558         strlcpy(
559             host.last_ip_addr, g_request->host.last_ip_addr,
560             sizeof(host.last_ip_addr)
561         );
562         host.nsame_ip_addr = 0;
563     } else {
564         host.nsame_ip_addr++;
565     }
566     host.on_frac = g_request->host.on_frac;
567     host.connected_frac = g_request->host.connected_frac;
568     host.active_frac = g_request->host.active_frac;
569     host.gpu_active_frac = g_request->host.gpu_active_frac;
570     host.cpu_and_network_available_frac = g_request->host.cpu_and_network_available_frac;
571     host.client_start_time = g_request->host.client_start_time;
572     host.previous_uptime = g_request->host.previous_uptime;
573     host.duration_correction_factor = g_request->host.duration_correction_factor;
574     host.p_ncpus = g_request->host.p_ncpus;
575     strlcpy(host.p_vendor, g_request->host.p_vendor, sizeof(host.p_vendor));
576         // unlikely this will change
577     strlcpy(host.p_model, g_request->host.p_model, sizeof(host.p_model));
578     host.p_fpops = g_request->host.p_fpops;
579     host.p_iops = g_request->host.p_iops;
580     host.p_membw = g_request->host.p_membw;
581     strlcpy(host.os_name, g_request->host.os_name, sizeof(host.os_name));
582     strlcpy(host.os_version, g_request->host.os_version, sizeof(host.os_version));
583     host.m_nbytes = g_request->host.m_nbytes;
584     host.m_cache = g_request->host.m_cache;
585     host.m_swap = g_request->host.m_swap;
586     host.d_total = g_request->host.d_total;
587     host.d_free = g_request->host.d_free;
588     host.d_boinc_used_total = g_request->host.d_boinc_used_total;
589     host.d_boinc_used_project = g_request->host.d_boinc_used_project;
590     host.n_bwup = g_request->host.n_bwup;
591     host.n_bwdown = g_request->host.n_bwdown;
592     if (strlen(g_request->host.host_cpid)) {
593         safe_strcpy(host.host_cpid, g_request->host.host_cpid);
594     }
595     strlcpy(host.product_name, g_request->host.product_name, sizeof(host.product_name));
596     host.fix_nans();
597 
598     return 0;
599 }
600 
601 // update the DB record to the values in "xhost"
602 // "initial_host" stores the current DB values;
603 // update only those fields that have changed
604 //
update_host_record(HOST & initial_host,HOST & xhost,USER & user)605 static int update_host_record(HOST& initial_host, HOST& xhost, USER& user) {
606     DB_HOST host;
607     int retval;
608     char buf[1024];
609 
610     host = xhost;
611 
612     // hash the CPID reported by the host with the user's email address.
613     // This prevents one user from spoofing another one's host.
614     //
615     if (strlen(host.host_cpid)) {
616         sprintf(buf, "%s%s", host.host_cpid, user.email_addr);
617         md5_block((const unsigned char*)buf, strlen(buf), host.host_cpid);
618     }
619 
620     const char* p = get_remote_addr();
621     if (p) {
622         strlcpy(host.external_ip_addr, p, sizeof(host.external_ip_addr));
623     }
624     retval = host.update_diff_sched(initial_host);
625     if (retval) {
626         log_messages.printf(MSG_CRITICAL,
627             "host.update() failed: %s\n", boincerror(retval)
628         );
629     }
630     return 0;
631 }
632 
reason_str(int n)633 inline const char* reason_str(int n) {
634     switch (n) {
635     case ABORT_REASON_NOT_FOUND: return "result not in request";
636     case ABORT_REASON_WU_CANCELLED: return "WU cancelled";
637     case ABORT_REASON_ASSIMILATED: return "WU assimilated";
638     case ABORT_REASON_TIMED_OUT: return "result timed out";
639     }
640     return "Unknown";
641 }
642 
643 // Figure out which of the results the host currently has
644 // should be aborted outright, or aborted if not started yet
645 //
send_result_abort()646 int send_result_abort() {
647     int aborts_sent = 0;
648     int retval = 0;
649     DB_IN_PROGRESS_RESULT result;
650     std::string result_names;
651     unsigned int i;
652 
653     if (g_request->other_results.size() == 0) {
654         return 0;
655     }
656 
657     // build list of result names
658     //
659     for (i=0; i<g_request->other_results.size(); i++) {
660         OTHER_RESULT& orp=g_request->other_results[i];
661         orp.abort = true;
662             // if the host has a result not in the DB, abort it
663         orp.abort_if_not_started = false;
664         orp.reason = ABORT_REASON_NOT_FOUND;
665         if (i > 0) result_names.append(", ");
666         result_names.append("'");
667         char buf[1024];
668         safe_strcpy(buf, orp.name);
669         escape_string(buf, sizeof(buf));
670         result_names.append(buf);
671         result_names.append("'");
672     }
673 
674     // look up selected fields from the results and their WUs,
675     // and decide if they should be aborted
676     //
677     while (!(retval = result.enumerate(g_reply->host.id, result_names.c_str()))) {
678         for (i=0; i<g_request->other_results.size(); i++) {
679             OTHER_RESULT& orp = g_request->other_results[i];
680             if (!strcmp(orp.name, result.result_name)) {
681                 if (result.error_mask&WU_ERROR_CANCELLED ) {
682                     // if the WU has been canceled, abort the result
683                     //
684                     orp.abort = true;
685                     orp.abort_if_not_started = false;
686                     orp.reason = ABORT_REASON_WU_CANCELLED;
687                 } else if (result.assimilate_state == ASSIMILATE_DONE) {
688                     // if the WU has been assimilated, abort if not started
689                     //
690                     orp.abort = false;
691                     orp.abort_if_not_started = true;
692                     orp.reason = ABORT_REASON_ASSIMILATED;
693                 } else if (result.server_state == RESULT_SERVER_STATE_OVER
694                     && result.outcome == RESULT_OUTCOME_NO_REPLY
695                 ) {
696                     // if timed out, abort if not started
697                     //
698                     orp.abort = false;
699                     orp.abort_if_not_started = true;
700                     orp.reason = ABORT_REASON_TIMED_OUT;
701                 } else {
702                     // all is good with the result - let it process
703                     orp.abort = false;
704                     orp.abort_if_not_started = false;
705                 }
706                 break;
707             }
708         }
709     }
710 
711     // If enumeration returned an error, don't send any aborts
712     //
713     if (retval && (retval != ERR_DB_NOT_FOUND)) {
714         return retval;
715     }
716 
717     // loop through the results and send the appropriate message (if any)
718     //
719     for (i=0; i<g_request->other_results.size(); i++) {
720         OTHER_RESULT& orp = g_request->other_results[i];
721         if (orp.abort) {
722             g_reply->result_aborts.push_back(orp.name);
723             log_messages.printf(MSG_NORMAL,
724                 "[HOST#%lu]: Send result_abort for result %s; reason: %s\n",
725                 g_reply->host.id, orp.name, reason_str(orp.reason)
726             );
727             // send user message
728             char buf[256];
729             sprintf(buf, "Result %s is no longer usable", orp.name);
730             g_reply->insert_message(buf, "low");
731         } else if (orp.abort_if_not_started) {
732             g_reply->result_abort_if_not_starteds.push_back(orp.name);
733             log_messages.printf(MSG_NORMAL,
734                 "[HOST#%lu]: Send result_abort_if_unstarted for result %s; reason %d\n",
735                 g_reply->host.id, orp.name, orp.reason
736             );
737         }
738     }
739 
740     return aborts_sent;
741 }
742 
743 // 1) Decide which global prefs to use for sched decisions: either
744 // - <working_global_prefs> from request msg
745 // - <global_prefs> from request message
746 // - prefs from user DB record
747 // and parse them into g_request->global_prefs.
748 // 2) update prefs in user record if needed
749 // 2) send global prefs in reply msg if needed
750 //
handle_global_prefs()751 int handle_global_prefs() {
752     char buf[BLOB_SIZE+256];
753     g_reply->send_global_prefs = false;
754     bool have_working_prefs = (strlen(g_request->working_global_prefs_xml)>0);
755     bool have_master_prefs = (strlen(g_request->global_prefs_xml)>0);
756         // absent if the host has host-specific prefs
757     bool have_db_prefs = (strlen(g_reply->user.global_prefs)>0);
758     bool same_account = !strcmp(
759         g_request->global_prefs_source_email_hash, g_reply->email_hash
760     );
761     double master_mod_time=0, db_mod_time=0, working_mod_time=0;
762     if (have_master_prefs) {
763         parse_double(g_request->global_prefs_xml, "<mod_time>", master_mod_time);
764         if (master_mod_time > dtime()) master_mod_time = dtime();
765     }
766     if (have_working_prefs) {
767         parse_double(g_request->working_global_prefs_xml, "<mod_time>", working_mod_time);
768         if (working_mod_time > dtime()) working_mod_time = dtime();
769     }
770     if (have_db_prefs) {
771         parse_double(g_reply->user.global_prefs, "<mod_time>", db_mod_time);
772         if (db_mod_time > dtime()) db_mod_time = dtime();
773     }
774 
775     if (config.debug_prefs) {
776         log_messages.printf(MSG_NORMAL,
777             "[prefs] have_master:%d have_working: %d have_db: %d\n",
778             have_master_prefs, have_working_prefs, have_db_prefs
779         );
780     }
781 
782     // decide which prefs to use for sched decisions,
783     // and parse them into g_request->global_prefs
784     //
785     if (have_working_prefs) {
786         g_request->global_prefs.parse(g_request->working_global_prefs_xml, "");
787         if (config.debug_prefs) {
788             log_messages.printf(MSG_NORMAL, "[prefs] using working prefs\n");
789         }
790     } else {
791         if (have_master_prefs) {
792             if (have_db_prefs && db_mod_time > master_mod_time) {
793                 g_request->global_prefs.parse(g_reply->user.global_prefs, g_reply->host.venue);
794                 if (config.debug_prefs) {
795                     log_messages.printf(MSG_NORMAL,
796                         "[prefs] using db prefs - more recent\n"
797                     );
798                 }
799             } else {
800                 g_request->global_prefs.parse(g_request->global_prefs_xml, g_reply->host.venue);
801                 if (config.debug_prefs) {
802                     log_messages.printf(MSG_NORMAL,
803                         "[prefs] using master prefs\n"
804                     );
805                 }
806             }
807         } else {
808             if (have_db_prefs) {
809                 g_request->global_prefs.parse(g_reply->user.global_prefs, g_reply->host.venue);
810                 if (config.debug_prefs) {
811                     log_messages.printf(MSG_NORMAL, "[prefs] using db prefs\n");
812                 }
813             } else {
814                 g_request->global_prefs.defaults();
815                 if (config.debug_prefs) {
816                     log_messages.printf(MSG_NORMAL, "[prefs] using default prefs\n");
817                 }
818             }
819         }
820     }
821 
822     // decide whether to update DB
823     //
824     if (!g_request->using_weak_auth && have_master_prefs) {
825         bool update_user_record = false;
826         if (have_db_prefs) {
827             if (master_mod_time > db_mod_time && same_account) {
828                 update_user_record = true;
829             }
830         } else {
831             if (same_account) update_user_record = true;
832         }
833         if (update_user_record) {
834             if (config.debug_prefs) {
835                 log_messages.printf(MSG_NORMAL, "[prefs] updating db prefs\n");
836             }
837             safe_strcpy(g_reply->user.global_prefs, g_request->global_prefs_xml);
838             DB_USER user;
839             user.id = g_reply->user.id;
840             escape_string(g_request->global_prefs_xml, sizeof(g_request->global_prefs_xml));
841             sprintf(buf, "global_prefs='%s'", g_request->global_prefs_xml);
842             unescape_string(g_request->global_prefs_xml, sizeof(g_request->global_prefs_xml));
843             int retval = user.update_field(buf);
844             if (retval) {
845                 log_messages.printf(MSG_CRITICAL,
846                     "user.update_field() failed: %s\n", boincerror(retval)
847                 );
848             }
849         }
850     }
851 
852     // decide whether to send DB prefs in reply msg
853     //
854     if (config.debug_prefs) {
855         log_messages.printf(MSG_NORMAL,
856             "[prefs] have DB prefs: %d; dbmod %f; global mod %f; working mod %f\n",
857             have_db_prefs, db_mod_time, g_request->global_prefs.mod_time, working_mod_time
858         );
859     }
860     if (have_db_prefs && db_mod_time > master_mod_time && db_mod_time > working_mod_time) {
861         if (config.debug_prefs) {
862             log_messages.printf(MSG_DEBUG,
863                 "[prefs] sending DB prefs in reply\n"
864             );
865         }
866         g_reply->send_global_prefs = true;
867     }
868     return 0;
869 }
870 
871 // if the client has an old code sign public key,
872 // send it the new one, with a signature based on the old one.
873 // If they don't have a code sign key, send them one.
874 // Return false if they have a key we don't recognize
875 // (in which case we won't send them work).
876 //
send_code_sign_key(char * code_sign_key)877 bool send_code_sign_key(char* code_sign_key) {
878     char* oldkey, *signature;
879     int i, retval;
880     char path[MAXPATHLEN];
881 
882     if (!strlen(g_request->code_sign_key)) {
883         safe_strcpy(g_reply->code_sign_key, code_sign_key);
884         return true;
885     }
886     if (!strcmp(g_request->code_sign_key, code_sign_key)) {
887         return true;
888     }
889 
890     log_messages.printf(MSG_NORMAL, "received old code sign key\n");
891 
892     // look for a signature file for the client's key.
893     // These are in pairs of files (N = 0, 1, ...)
894     // old_key_N: contains an old key
895     // signature_N: contains a signature for new key,
896     // based on the old key
897     // signature_stripped_N: signature for new key w/ trailing \n removed
898     // (needed for 7.0+ clients, which strip trailing whitespace)
899     //
900     // A project can have several of these if it wants,
901     // e.g. if it changes keys a lot.
902     //
903     for (i=0; ; i++) {
904         sprintf(path, "%s/old_key_%d", config.key_dir, i);
905         retval = read_file_malloc(path, oldkey);
906         if (retval) {
907             // we've scanned all the signature files and
908             // didn't find one that worked.
909             // User must reattach.
910             //
911             log_messages.printf(MSG_CRITICAL,
912                 "scanned old_key_i files, can find client's key\n"
913             );
914             break;
915         }
916         strip_whitespace(oldkey);
917         if (!strcmp(oldkey, g_request->code_sign_key)) {
918             // We've found the client's key.
919             // Get the signature for the new key.
920             //
921             if (g_request->core_client_major_version < 7) {
922                 sprintf(path, "%s/signature_%d", config.key_dir, i);
923             } else {
924                 sprintf(path, "%s/signature_stripped_%d", config.key_dir, i);
925             }
926             retval = read_file_malloc(path, signature);
927             if (retval) {
928                 // project is missing the signature file.
929                 // Tell the user to reattach.
930                 //
931                 log_messages.printf(MSG_CRITICAL,
932                     "Missing signature file for old key %d\n", i
933                 );
934                 free(oldkey);
935                 break;
936             } else {
937                 log_messages.printf(MSG_NORMAL,
938                     "sending new code sign key and signature\n"
939                 );
940                 safe_strcpy(g_reply->code_sign_key, code_sign_key);
941                 safe_strcpy(g_reply->code_sign_key_signature, signature);
942                 free(signature);
943                 free(oldkey);
944                 return true;
945             }
946         }
947         free(oldkey);
948     }
949 
950     g_reply->insert_message(
951        _("The project has changed its security key.  Please remove and add this project."),
952        "notice"
953     );
954     return false;
955 }
956 
957 // If <min_core_client_version_announced> is set,
958 // and the core client version is less than this version,
959 // send a warning to users to upgrade before deadline
960 // <min_core_client_upgrade_deadline>
961 //
warn_user_if_core_client_upgrade_scheduled()962 void warn_user_if_core_client_upgrade_scheduled() {
963     if (g_request->core_client_version < config.min_core_client_version_announced) {
964 
965         // time remaining in hours, before upgrade required
966         int remaining = config.min_core_client_upgrade_deadline-time(0);
967         remaining /= 3600;
968 
969         if (remaining > 0) {
970 
971             char msg[512];
972             int days  = remaining / 24;
973             int hours = remaining % 24;
974 
975             sprintf(msg,
976                 "In %d days and %d hours, this project will require a minimum "
977                 "BOINC version of %d.%d.%d.  You are currently using "
978                 "version %d.%d.%d; please upgrade before this time.",
979                 days, hours,
980                 config.min_core_client_version_announced / 10000,
981                 (config.min_core_client_version_announced / 100)%100,
982                 config.min_core_client_version_announced % 100,
983                 g_request->core_client_major_version,
984                 g_request->core_client_minor_version,
985                 g_request->core_client_release
986             );
987             // make this low priority until three days are left.  Then
988             // bump to high.
989             //
990             if (days<3) {
991                 g_reply->insert_message(msg, "notice");
992             } else {
993                 g_reply->insert_message(msg, "low");
994             }
995             log_messages.printf(MSG_DEBUG,
996                 "Sending warning: upgrade client %d.%d.%d within %d days %d hours\n",
997                 g_request->core_client_major_version,
998                 g_request->core_client_minor_version,
999                 g_request->core_client_release,
1000                 days, hours
1001             );
1002         }
1003     }
1004     return;
1005 }
1006 
unacceptable_os()1007 bool unacceptable_os() {
1008     unsigned int i;
1009     char buf[1024];
1010 
1011     for (i=0; i<config.ban_os->size(); i++) {
1012         regex_t& re = (*config.ban_os)[i];
1013         safe_strcpy(buf, g_request->host.os_name);
1014         safe_strcat(buf, "\t");
1015         safe_strcat(buf, g_request->host.os_version);
1016         if (!regexec(&re, buf, 0, NULL, 0)) {
1017             log_messages.printf(MSG_NORMAL,
1018                 "Unacceptable OS %s %s\n",
1019                 g_request->host.os_name, g_request->host.os_version
1020             );
1021             sprintf(buf, "%s %s %s",
1022                 _("This project doesn't support operating system"),
1023                 g_request->host.os_name, g_request->host.os_version
1024             );
1025             g_reply->insert_message(buf, "notice");
1026             g_reply->set_delay(DELAY_UNACCEPTABLE_OS);
1027             return true;
1028         }
1029     }
1030     return false;
1031 }
1032 
unacceptable_cpu()1033 bool unacceptable_cpu() {
1034     unsigned int i;
1035     char buf[1024];
1036 
1037     for (i=0; i<config.ban_cpu->size(); i++) {
1038         regex_t& re = (*config.ban_cpu)[i];
1039         safe_strcpy(buf, g_request->host.p_vendor);
1040         safe_strcat(buf, "\t");
1041         safe_strcat(buf, g_request->host.p_model);
1042         if (!regexec(&re, buf, 0, NULL, 0)) {
1043             log_messages.printf(MSG_NORMAL,
1044                 "Unacceptable CPU %s %s\n",
1045                 g_request->host.p_vendor, g_request->host.p_model
1046             );
1047             sprintf(buf, "%s %s %s",
1048                 _("This project doesn't support CPU type"),
1049                 g_request->host.p_vendor, g_request->host.p_model
1050             );
1051             g_reply->insert_message(buf, "notice");
1052             g_reply->set_delay(DELAY_UNACCEPTABLE_OS);
1053             return true;
1054         }
1055     }
1056     return false;
1057 }
1058 
wrong_core_client_version()1059 bool wrong_core_client_version() {
1060     if (!config.min_core_client_version) {
1061         return false;
1062     }
1063     if (g_request->core_client_version >= config.min_core_client_version) {
1064         return false;
1065     }
1066     log_messages.printf(MSG_NORMAL,
1067         "[HOST#%lu] Wrong client version from user: wanted %d, got %d\n",
1068         g_request->hostid,
1069         config.min_core_client_version, g_request->core_client_minor_version
1070     );
1071     g_reply->insert_message(
1072         _("Your BOINC client software is too old.  Please install the current version."),
1073         "notice"
1074     );
1075     g_reply->set_delay(DELAY_BAD_CLIENT_VERSION);
1076     return true;
1077 }
1078 
handle_msgs_from_host()1079 void handle_msgs_from_host() {
1080     unsigned int i;
1081     DB_MSG_FROM_HOST mfh;
1082     int retval;
1083 
1084     for (i=0; i<g_request->msgs_from_host.size(); i++) {
1085         g_reply->send_msg_ack = true;
1086         MSG_FROM_HOST_DESC& md = g_request->msgs_from_host[i];
1087         mfh.clear();
1088         mfh.create_time = time(0);
1089         safe_strcpy(mfh.variety, md.variety);
1090         mfh.hostid = g_reply->host.id;
1091         mfh.handled = false;
1092         safe_strcpy(mfh.xml, md.msg_text.c_str());
1093         log_messages.printf(MSG_NORMAL,
1094             "got msg from host; variety %s \n",
1095             mfh.variety
1096         );
1097         retval = mfh.insert();
1098         if (retval) {
1099             log_messages.printf(MSG_CRITICAL,
1100                 "[HOST#%lu] message insert failed: %s\n",
1101                 g_reply->host.id, boincerror(retval)
1102             );
1103             g_reply->send_msg_ack = false;
1104 
1105             // may as well return; if one insert failed, others will too
1106             //
1107             return;
1108         }
1109     }
1110 }
1111 
handle_msgs_to_host()1112 void handle_msgs_to_host() {
1113     DB_MSG_TO_HOST mth;
1114     char buf[256];
1115     sprintf(buf, "where hostid = %lu and handled = %d", g_reply->host.id, 0);
1116     while (!mth.enumerate(buf)) {
1117         g_reply->msgs_to_host.push_back(mth);
1118         mth.handled = true;
1119         mth.update();
1120     }
1121 }
1122 
log_request()1123 static void log_request() {
1124     log_messages.printf(MSG_NORMAL,
1125         "Request: [USER#%lu] [HOST#%lu] [IP %s] client %d.%d.%d\n",
1126         g_reply->user.id, g_reply->host.id, get_remote_addr(),
1127         g_request->core_client_major_version,
1128         g_request->core_client_minor_version,
1129         g_request->core_client_release
1130     );
1131     if (config.debug_request_details) {
1132         log_messages.printf(MSG_DEBUG,
1133              "Request details: auth %s, RPC seqno %d, platform %s\n",
1134              g_request->authenticator,
1135              g_request->rpc_seqno,
1136              g_request->platform.name
1137         );
1138     }
1139     log_messages.set_indent_level(2);
1140 }
1141 
bad_install_type()1142 bool bad_install_type() {
1143     if (config.no_vista_sandbox) {
1144         if (!strcmp(g_request->host.os_name, "Microsoft Windows Vista")) {
1145             if (g_request->sandbox == 1) {
1146                 log_messages.printf(MSG_NORMAL,
1147                     "Vista secure install - not sending work\n"
1148                 );
1149                 g_reply->insert_message(
1150                     "Unable to send work to Vista with BOINC installed in protected mode.  Please reinstall BOINC and uncheck 'Protected application execution'",
1151                     "notice"
1152                 );
1153             }
1154         }
1155     }
1156     return false;
1157 }
1158 
requesting_work()1159 static inline bool requesting_work() {
1160     if (g_request->dont_send_work) return false;
1161     if (g_request->work_req_seconds > 0) return true;
1162     if (g_request->cpu_req_secs > 0) return true;
1163     for (int i=1; i<NPROC_TYPES; i++) {
1164         COPROC* cp = g_request->coprocs.proc_type_to_coproc(i);
1165         if (cp && cp->count && cp->req_secs) return true;
1166     }
1167     if (ssp->have_nci_app) return true;
1168     return false;
1169 }
1170 
process_request(char * code_sign_key)1171 void process_request(char* code_sign_key) {
1172     PLATFORM* platform;
1173     int retval;
1174     double last_rpc_time, x;
1175     struct tm *rpc_time_tm;
1176     bool ok_to_send_work = !config.dont_send_jobs;
1177     bool have_no_work = false;
1178     char buf[256];
1179     HOST initial_host;
1180     unsigned int i;
1181     time_t t;
1182 
1183     memset(&g_reply->wreq, 0, sizeof(g_reply->wreq));
1184 
1185     // if client has sticky files we don't need any more, tell it
1186     //
1187     do_file_delete_regex();
1188 
1189     // if different major version of BOINC, just send a message
1190     //
1191     if (wrong_core_client_version()
1192         || unacceptable_os()
1193         || unacceptable_cpu()
1194     ) {
1195         ok_to_send_work = false;
1196     }
1197 
1198     // if no jobs reported and none to send, return without accessing DB
1199     //
1200     if (!ok_to_send_work && !g_request->results.size()) {
1201         return;
1202     }
1203 
1204     warn_user_if_core_client_upgrade_scheduled();
1205 
1206     g_wreq->no_jobs_available = false;
1207     if (requesting_work()) {
1208         if (config.locality_scheduling || config.locality_scheduler_fraction || config.enable_assignment) {
1209             have_no_work = false;
1210         } else {
1211             lock_sema();
1212             have_no_work = ssp->no_work(g_pid);
1213             if (have_no_work) {
1214                 g_wreq->no_jobs_available = true;
1215             }
1216             unlock_sema();
1217         }
1218     }
1219 
1220     // If:
1221     // - there's no work,
1222     // - a config flag is set,
1223     // - client isn't returning results,
1224     // - this isn't an initial RPC,
1225     // - client is requesting work
1226     // then return without accessing the DB.
1227     // This is an efficiency hack for when servers are overloaded
1228     //
1229     if (
1230         have_no_work
1231         && config.nowork_skip
1232         && requesting_work()
1233         && (g_request->results.size() == 0)
1234         && (g_request->hostid != 0)
1235     ) {
1236         g_reply->insert_message("No work available", "low");
1237         g_reply->set_delay(DELAY_NO_WORK_SKIP);
1238         if (!config.msg_to_host && !config.enable_vda) {
1239             log_messages.printf(MSG_NORMAL, "No work - skipping DB access\n");
1240             return;
1241         }
1242     }
1243 
1244     // FROM HERE ON DON'T RETURN; "goto leave" instead
1245     // (because ssp->no_work() may have tagged an entry in the work array
1246     // with our process ID)
1247 
1248     retval = open_database();
1249     if (retval) {
1250         send_error_message("Server can't open database", config.maintenance_delay);
1251         g_reply->project_is_down = true;
1252         goto leave;
1253     }
1254 
1255     retval = authenticate_user();
1256     if (retval) goto leave;
1257     if (g_reply->user.id == 0) {
1258         log_messages.printf(MSG_CRITICAL, "No user ID!\n");
1259     }
1260     initial_host = g_reply->host;
1261     g_reply->host.rpc_seqno = g_request->rpc_seqno;
1262 
1263     g_reply->nucleus_only = false;
1264 
1265     log_request();
1266 
1267 #if 0
1268     // if you need to debug a problem w/ a particular host or user,
1269     // edit the following
1270     //
1271     if (g_reply->user.id == XX || g_reply.host.id == YY) {
1272         config.sched_debug_level = 3;
1273         config.debug_send = true;
1274         ...
1275     }
1276 #endif
1277 
1278     // is host blacklisted?
1279     //
1280     if (g_reply->host._max_results_day == -1) {
1281         send_error_message("Not accepting requests from this host", 86400);
1282         goto leave;
1283     }
1284 
1285     if (strlen(config.sched_lockfile_dir)) {
1286         int pid_with_lock = lock_sched();
1287         if (pid_with_lock > 0) {
1288             log_messages.printf(MSG_CRITICAL,
1289                 "Another scheduler instance [PID=%d] is running for [HOST#%lu]\n",
1290                 pid_with_lock, g_reply->host.id
1291             );
1292         } else if (pid_with_lock) {
1293             log_messages.printf(MSG_CRITICAL,
1294                 "Error acquiring lock for [HOST#%lu]\n", g_reply->host.id
1295             );
1296         }
1297         if (pid_with_lock) {
1298             send_error_message(
1299                 "Another scheduler instance is running for this host", 60
1300             );
1301             goto leave;
1302         }
1303     }
1304 
1305     // in deciding whether it's a new day,
1306     // add a random factor (based on host ID)
1307     // to smooth out network traffic over the day
1308     //
1309     retval = rand();
1310     srand(g_reply->host.id);
1311     x = drand()*86400;
1312     srand(retval);
1313     last_rpc_time = g_reply->host.rpc_time;
1314     t = (time_t)(g_reply->host.rpc_time + x);
1315     rpc_time_tm = localtime(&t);
1316     g_request->last_rpc_dayofyear = rpc_time_tm->tm_yday;
1317 
1318     t = time(0);
1319     g_reply->host.rpc_time = t;
1320     t += (time_t)x;
1321     rpc_time_tm = localtime(&t);
1322     g_request->current_rpc_dayofyear = rpc_time_tm->tm_yday;
1323 
1324     retval = modify_host_struct(g_reply->host);
1325 
1326     // write time stats to disk if present
1327     //
1328     if (g_request->have_time_stats_log) {
1329         write_time_stats_log();
1330     }
1331 
1332     // look up the client's platform(s) in the DB
1333     //
1334     platform = ssp->lookup_platform(g_request->platform.name);
1335     if (platform) g_request->platforms.list.push_back(platform);
1336 
1337     // if primary platform is anonymous, ignore alternate platforms
1338     //
1339     if (strcmp(g_request->platform.name, "anonymous")) {
1340         for (i=0; i<g_request->alt_platforms.size(); i++) {
1341             platform = ssp->lookup_platform(g_request->alt_platforms[i].name);
1342             if (platform) g_request->platforms.list.push_back(platform);
1343         }
1344     }
1345     if (g_request->platforms.list.size() == 0) {
1346         sprintf(buf, "%s %s",
1347             _("This project doesn't support computers of type"),
1348             g_request->platform.name
1349         );
1350         g_reply->insert_message(buf, "notice");
1351         log_messages.printf(MSG_CRITICAL,
1352             "[HOST#%lu] platform '%s' not found\n",
1353             g_reply->host.id, g_request->platform.name
1354         );
1355         g_reply->set_delay(DELAY_PLATFORM_UNSUPPORTED);
1356         goto leave;
1357     }
1358 
1359     handle_global_prefs();
1360 
1361     read_host_app_versions();
1362     update_n_jobs_today();
1363 
1364     handle_results();
1365     handle_file_xfer_results();
1366     if (config.enable_vda) {
1367         handle_vda();
1368     }
1369 
1370     // Do this before resending lost jobs
1371     //
1372     if (bad_install_type()) {
1373         ok_to_send_work = false;
1374     }
1375     if (!requesting_work()) {
1376         ok_to_send_work = false;
1377     }
1378     send_work_setup();
1379 
1380     if (g_request->have_other_results_list) {
1381         if (ok_to_send_work
1382             && (config.resend_lost_results || g_wreq->resend_lost_results)
1383             && !g_request->results_truncated
1384         ) {
1385             if (resend_lost_work()) {
1386                 if (config.debug_send) {
1387                     log_messages.printf(MSG_NORMAL,
1388                         "[send] Resent lost jobs, don't send more\n"
1389                     );
1390                 }
1391                 ok_to_send_work = false;
1392             }
1393         }
1394         if (config.send_result_abort) {
1395             send_result_abort();
1396         }
1397     }
1398 
1399     if (requesting_work()) {
1400         if (!send_code_sign_key(code_sign_key)) {
1401             ok_to_send_work = false;
1402         }
1403 
1404         if (have_no_work) {
1405             if (config.debug_send) {
1406                 log_messages.printf(MSG_NORMAL,
1407                     "[send] No jobs in shmem cache\n"
1408                 );
1409             }
1410         }
1411 
1412         // if last RPC was within config.min_sendwork_interval, don't send work
1413         //
1414         if (!have_no_work && ok_to_send_work) {
1415             if (config.min_sendwork_interval) {
1416                 double diff = dtime() - last_rpc_time;
1417                 if (diff < config.min_sendwork_interval) {
1418                     ok_to_send_work = false;
1419                     log_messages.printf(MSG_NORMAL,
1420                         "Not sending work - last request too recent: %f\n", diff
1421                     );
1422                     sprintf(buf,
1423                         "Not sending work - last request too recent: %d sec", (int)diff
1424                     );
1425                     g_reply->insert_message(buf, "low");
1426 
1427                     // the 1.01 is in case client's clock
1428                     // is slightly faster than ours
1429                     //
1430                     g_reply->set_delay(1.01*config.min_sendwork_interval);
1431                 }
1432             }
1433             if (ok_to_send_work) {
1434                 send_work();
1435             }
1436         }
1437         if (g_wreq->no_jobs_available) {
1438             g_reply->insert_message("Project has no tasks available", "low");
1439         }
1440     }
1441 
1442 
1443     handle_msgs_from_host();
1444     if (config.msg_to_host) {
1445         handle_msgs_to_host();
1446     }
1447 
1448     // compute GPU params
1449     //
1450     g_reply->host.p_ngpus = 0;
1451     g_reply->host.p_gpu_fpops = 0;
1452     for (int j=1; j<g_request->coprocs.n_rsc; j++) {
1453         int n = g_request->coprocs.coprocs[j].count;
1454         g_reply->host.p_ngpus += n;
1455         g_reply->host.p_gpu_fpops += n*g_request->coprocs.coprocs[j].peak_flops;
1456     }
1457 
1458     update_host_record(initial_host, g_reply->host, g_reply->user);
1459     write_host_app_versions();
1460 
1461 leave:
1462     if (!have_no_work) {
1463         ssp->restore_work(g_pid);
1464     }
1465 }
1466 
log_incomplete_request()1467 static void log_incomplete_request() {
1468     // BOINC scheduler requests use method POST.
1469     // So method GET means that someone is trying a browser.
1470     //
1471     char *rm=getenv("REQUEST_METHOD");
1472     bool used_get = false;
1473     if (rm && !strcmp(rm, "GET")) {
1474         used_get = true;
1475     }
1476     log_messages.printf(MSG_NORMAL,
1477         "Incomplete request received %sfrom IP %s, auth %s, platform %s, version %d.%d.%d\n",
1478         used_get?"(used GET method - probably a browser) ":"",
1479         get_remote_addr(), g_request->authenticator, g_request->platform.name,
1480         g_request->core_client_major_version, g_request->core_client_minor_version,
1481         g_request->core_client_release
1482     );
1483 }
1484 
log_user_messages()1485 static void log_user_messages() {
1486     for (unsigned int i=0; i<g_reply->messages.size(); i++) {
1487         USER_MESSAGE um = g_reply->messages[i];
1488         log_messages.printf(MSG_NORMAL,
1489             "[user_messages] [HOST#%lu] MSG(%s) %s\n",
1490             g_reply->host.id, um.priority.c_str(), um.message.c_str()
1491         );
1492     }
1493 }
1494 
handle_request(FILE * fin,FILE * fout,char * code_sign_key)1495 void handle_request(FILE* fin, FILE* fout, char* code_sign_key) {
1496     SCHEDULER_REQUEST sreq;
1497     SCHEDULER_REPLY sreply;
1498     char buf[1024];
1499 
1500     g_request = &sreq;
1501     g_reply = &sreply;
1502     g_wreq = &sreply.wreq;
1503 
1504     sreply.nucleus_only = true;
1505 
1506     log_messages.set_indent_level(1);
1507 
1508     MIOFILE mf;
1509     XML_PARSER xp(&mf);
1510     mf.init_file(fin);
1511     const char* p = sreq.parse(xp);
1512     double start_time = dtime();
1513     if (!p){
1514         process_request(code_sign_key);
1515 
1516         if ((config.locality_scheduling || config.locality_scheduler_fraction) && !sreply.nucleus_only) {
1517             send_file_deletes();
1518         }
1519     } else {
1520         sprintf(buf, "Error in request message: %s", p);
1521         log_incomplete_request();
1522         sreply.insert_message(buf, "low");
1523     }
1524 
1525     if (config.debug_user_messages) {
1526         log_user_messages();
1527     }
1528 
1529     sreply.write(fout, sreq);
1530     log_messages.printf(MSG_NORMAL,
1531         "Scheduler ran %.3f seconds\n", dtime()-start_time
1532     );
1533 
1534     if (strlen(config.sched_lockfile_dir)) {
1535         unlock_sched();
1536     }
1537 }
1538 
1539 const char *BOINC_RCSID_2ac231f9de = "$Id$";
1540