1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2019 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern Sibbald, September MM
25  */
26 /**
27  * @file
28  * User Agent Output Commands
29  *
30  * I.e. messages, listing database, showing resources, ...
31  */
32 
33 #include "include/bareos.h"
34 #include "dird.h"
35 #include "dird/dird_globals.h"
36 #include "dird/get_database_connection.h"
37 #include "dird/jcr_private.h"
38 #include "dird/job.h"
39 #include "dird/ua_cmdstruct.h"
40 #include "cats/sql_pooling.h"
41 #include "dird/next_vol.h"
42 #include "dird/ua_db.h"
43 #include "dird/ua_output.h"
44 #include "dird/ua_select.h"
45 #include "dird/show_cmd_available_resources.h"
46 #include "lib/edit.h"
47 #include "lib/parse_conf.h"
48 
49 namespace directordaemon {
50 
51 /* Imported subroutines */
52 
53 /* Imported variables */
54 extern struct s_jl joblevels[];
55 
56 /* Imported functions */
57 
58 /* Forward referenced functions */
59 static bool DoListCmd(UaContext* ua, const char* cmd, e_list_type llist);
60 static bool ListNextvol(UaContext* ua, int ndays);
61 static bool ParseListBackupsCmd(UaContext* ua,
62                                 const char* range,
63                                 e_list_type llist);
64 
65 /**
66  * Some defaults.
67  */
68 #define DEFAULT_LOG_LINES 5
69 #define DEFAULT_NR_DAYS 50
70 
71 /**
72  * Turn auto display of console messages on/off
73  */
AutodisplayCmd(UaContext * ua,const char * cmd)74 bool AutodisplayCmd(UaContext* ua, const char* cmd)
75 {
76   static const char* kw[] = {NT_("on"), NT_("off"), NULL};
77 
78   switch (FindArgKeyword(ua, kw)) {
79     case 0:
80       ua->auto_display_messages = true;
81       break;
82     case 1:
83       ua->auto_display_messages = false;
84       break;
85     default:
86       ua->ErrorMsg(_("ON or OFF keyword missing.\n"));
87       break;
88   }
89   return true;
90 }
91 
92 /**
93  * Turn GUI mode on/off
94  */
gui_cmd(UaContext * ua,const char * cmd)95 bool gui_cmd(UaContext* ua, const char* cmd)
96 {
97   static const char* kw[] = {NT_("on"), NT_("off"), NULL};
98 
99   switch (FindArgKeyword(ua, kw)) {
100     case 0:
101       ua->jcr->gui = ua->gui = true;
102       break;
103     case 1:
104       ua->jcr->gui = ua->gui = false;
105       break;
106     default:
107       ua->ErrorMsg(_("ON or OFF keyword missing.\n"));
108       break;
109   }
110   return true;
111 }
112 
113 /**
114  * Enter with Resources locked
115  */
ShowDisabledJobs(UaContext * ua)116 static void ShowDisabledJobs(UaContext* ua)
117 {
118   JobResource* job;
119   bool first = true;
120 
121   foreach_res (job, R_JOB) {
122     if (!ua->AclAccessOk(Job_ACL, job->resource_name_, false)) { continue; }
123 
124     if (!job->enabled) {
125       if (first) {
126         first = false;
127         ua->SendMsg(_("Disabled Jobs:\n"));
128       }
129       ua->SendMsg("   %s\n", job->resource_name_);
130     }
131   }
132 
133   if (first) { ua->SendMsg(_("No disabled Jobs.\n")); }
134 }
135 
136 /**
137  * Enter with Resources locked
138  */
ShowDisabledClients(UaContext * ua)139 static void ShowDisabledClients(UaContext* ua)
140 {
141   ClientResource* client;
142   bool first = true;
143 
144   foreach_res (client, R_CLIENT) {
145     if (!ua->AclAccessOk(Client_ACL, client->resource_name_, false)) {
146       continue;
147     }
148 
149     if (!client->enabled) {
150       if (first) {
151         first = false;
152         ua->SendMsg(_("Disabled Clients:\n"));
153       }
154       ua->SendMsg("   %s\n", client->resource_name_);
155     }
156   }
157 
158   if (first) { ua->SendMsg(_("No disabled Clients.\n")); }
159 }
160 
161 /**
162  * Enter with Resources locked
163  */
ShowDisabledSchedules(UaContext * ua)164 static void ShowDisabledSchedules(UaContext* ua)
165 {
166   ScheduleResource* sched;
167   bool first = true;
168 
169   foreach_res (sched, R_SCHEDULE) {
170     if (!ua->AclAccessOk(Schedule_ACL, sched->resource_name_, false)) {
171       continue;
172     }
173 
174     if (!sched->enabled) {
175       if (first) {
176         first = false;
177         ua->SendMsg(_("Disabled Schedules:\n"));
178       }
179       ua->SendMsg("   %s\n", sched->resource_name_);
180     }
181   }
182 
183   if (first) { ua->SendMsg(_("No disabled Schedules.\n")); }
184 }
185 
186 /**
187  * Enter with Resources locked
188  */
ShowAll(UaContext * ua,bool hide_sensitive_data,bool verbose)189 static void ShowAll(UaContext* ua, bool hide_sensitive_data, bool verbose)
190 {
191   for (int j = my_config->r_first_; j <= my_config->r_last_; j++) {
192     switch (j) {
193       case R_DEVICE:
194         /*
195          * Skip R_DEVICE since it is really not used or updated
196          */
197         continue;
198       default:
199         if (my_config->res_head_[j - my_config->r_first_]) {
200           my_config->DumpResourceCb_(
201               j, my_config->res_head_[j - my_config->r_first_], bsendmsg, ua,
202               hide_sensitive_data, verbose);
203         }
204         break;
205     }
206   }
207 }
208 
209 /**
210  *  Displays Resources
211  *
212  *  show
213  *  show all
214  *  show <resource-keyword-name> - e.g. show directors
215  *  show <resource-keyword-name>=<name> - e.g. show director=HeadMan
216  *  show disabled - shows disabled jobs, clients and schedules
217  *  show disabled jobs - shows disabled jobs
218  *  show disabled clients - shows disabled clients
219  *  show disabled schedules - shows disabled schedules
220  */
show_cmd(UaContext * ua,const char * cmd)221 bool show_cmd(UaContext* ua, const char* cmd)
222 {
223   int i, j, type, len;
224   int recurse;
225   char* res_name;
226   BareosResource* res = NULL;
227   bool verbose = false;
228   bool hide_sensitive_data;
229 
230   Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
231 
232   /*
233    * When the console has no access to the configure cmd then any show cmd
234    * will suppress all sensitive information like for instance passwords.
235    */
236   hide_sensitive_data = !ua->AclAccessOk(Command_ACL, "configure", false);
237 
238   if (FindArg(ua, NT_("verbose")) > 0) { verbose = true; }
239 
240   LockRes(my_config);
241 
242   /*
243    * Without parameter, show all ressources.
244    */
245   if (ua->argc == 1) { ShowAll(ua, hide_sensitive_data, verbose); }
246 
247   for (i = 1; i < ua->argc; i++) {
248     /*
249      * skip verbose keyword, already handled earlier.
250      */
251     if (Bstrcasecmp(ua->argk[i], NT_("verbose"))) { continue; }
252 
253     if (Bstrcasecmp(ua->argk[i], NT_("disabled"))) {
254       if (((i + 1) < ua->argc) && Bstrcasecmp(ua->argk[i + 1], NT_("jobs"))) {
255         ShowDisabledJobs(ua);
256       } else if (((i + 1) < ua->argc) &&
257                  Bstrcasecmp(ua->argk[i + 1], NT_("clients"))) {
258         ShowDisabledClients(ua);
259       } else if (((i + 1) < ua->argc) &&
260                  Bstrcasecmp(ua->argk[i + 1], NT_("schedules"))) {
261         ShowDisabledSchedules(ua);
262       } else {
263         ShowDisabledJobs(ua);
264         ShowDisabledClients(ua);
265         ShowDisabledSchedules(ua);
266       }
267 
268       goto bail_out;
269     }
270 
271     type = 0;
272     res_name = ua->argk[i];
273     if (!ua->argv[i]) { /* was a name given? */
274       /*
275        * No name, dump all resources of specified type
276        */
277       recurse = 1;
278       len = strlen(res_name);
279       for (j = 0; show_cmd_available_resources[j].res_name; j++) {
280         if (bstrncasecmp(res_name, _(show_cmd_available_resources[j].res_name),
281                          len)) {
282           type = show_cmd_available_resources[j].type;
283           if (type > 0) {
284             res = my_config->res_head_[type - my_config->r_first_];
285           } else {
286             res = NULL;
287           }
288           break;
289         }
290       }
291     } else {
292       /*
293        * Dump a single resource with specified name
294        */
295       recurse = 0;
296       len = strlen(res_name);
297       for (j = 0; show_cmd_available_resources[j].res_name; j++) {
298         if (bstrncasecmp(res_name, _(show_cmd_available_resources[j].res_name),
299                          len)) {
300           type = show_cmd_available_resources[j].type;
301           res = (BareosResource*)ua->GetResWithName(type, ua->argv[i], true);
302           if (!res) { type = -3; }
303           break;
304         }
305       }
306     }
307 
308     switch (type) {
309       case -1: /* all */
310         ShowAll(ua, hide_sensitive_data, verbose);
311         break;
312       case -2:
313         ua->SendMsg(_("Keywords for the show command are:\n"));
314         for (j = 0; show_cmd_available_resources[j].res_name; j++) {
315           ua->ErrorMsg("%s\n", _(show_cmd_available_resources[j].res_name));
316         }
317         goto bail_out;
318       case -3:
319         ua->ErrorMsg(_("%s resource %s not found.\n"), res_name, ua->argv[i]);
320         goto bail_out;
321       case 0:
322         ua->ErrorMsg(_("Resource %s not found\n"), res_name);
323         goto bail_out;
324       default:
325         my_config->DumpResourceCb_(recurse ? type : -type, res, bsendmsg, ua,
326                                    hide_sensitive_data, verbose);
327         break;
328     }
329   }
330 
331 bail_out:
332   UnlockRes(my_config);
333   return true;
334 }
335 
336 /**
337  *  List contents of database
338  *
339  *  list jobs                   - lists all jobs run
340  *  list jobid=nnn              - list job data for jobid
341  *  list ujobid=uname           - list job data for unique jobid
342  *  list job=name               - list all jobs with "name"
343  *  list jobname=name           - same as above
344  *  list jobmedia jobid=nnn
345  *  list jobmedia ujobid=uname
346  *  list joblog jobid=<nn>
347  *  list joblog job=name
348  *  list log [ limit=<number> [ offset=<number> ] ]
349  *  list basefiles jobid=nnn    - list files saved for job nn
350  *  list basefiles ujobid=uname
351  *  list files jobid=<nn>       - list files saved for job nn
352  *  list files ujobid=name
353  *  list pools                  - list pool records
354  *  list jobtotals              - list totals for all jobs
355  *  list media                  - list media for given pool (deprecated)
356  *  list volumes                - list Volumes
357  *  list clients                - list clients
358  *  list nextvol job=xx         - list the next vol to be used by job
359  *  list nextvolume job=xx      - same as above.
360  *  list copies jobid=x,y,z
361  */
362 
363 /* Do long or full listing */
LlistCmd(UaContext * ua,const char * cmd)364 bool LlistCmd(UaContext* ua, const char* cmd)
365 {
366   return DoListCmd(ua, cmd, VERT_LIST);
367 }
368 
369 /* Do short or summary listing */
list_cmd(UaContext * ua,const char * cmd)370 bool list_cmd(UaContext* ua, const char* cmd)
371 {
372   return DoListCmd(ua, cmd, HORZ_LIST);
373 }
374 
GetJobidFromCmdline(UaContext * ua)375 static int GetJobidFromCmdline(UaContext* ua)
376 {
377   int i, jobid;
378   JobDbRecord jr;
379   ClientDbRecord cr;
380 
381   i = FindArgWithValue(ua, NT_("ujobid"));
382   if (i >= 0) {
383     bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
384     jr.JobId = 0;
385     if (ua->db->GetJobRecord(ua->jcr, &jr)) {
386       jobid = jr.JobId;
387     } else {
388       return -1;
389     }
390   } else {
391     i = FindArgWithValue(ua, NT_("jobid"));
392     if (i >= 0) {
393       jr.JobId = str_to_int64(ua->argv[i]);
394     } else {
395       jobid = 0;
396       goto bail_out;
397     }
398   }
399 
400   if (ua->db->GetJobRecord(ua->jcr, &jr)) {
401     jobid = jr.JobId;
402   } else {
403     Dmsg1(200, "GetJobidFromCmdline: Failed to get job record for JobId %d\n",
404           jr.JobId);
405     jobid = -1;
406     goto bail_out;
407   }
408 
409   if (!ua->AclAccessOk(Job_ACL, jr.Name, true)) {
410     Dmsg1(200, "GetJobidFromCmdline: No access to Job %s\n", jr.Name);
411     jobid = -1;
412     goto bail_out;
413   }
414 
415   if (jr.ClientId) {
416     cr.ClientId = jr.ClientId;
417     if (ua->db->GetClientRecord(ua->jcr, &cr)) {
418       if (!ua->AclAccessOk(Client_ACL, cr.Name, true)) {
419         Dmsg1(200, "GetJobidFromCmdline: No access to Client %s\n", cr.Name);
420         jobid = -1;
421         goto bail_out;
422       }
423     } else {
424       Dmsg1(
425           200,
426           "GetJobidFromCmdline: Failed to get client record for ClientId %d\n",
427           jr.ClientId);
428       jobid = -1;
429       goto bail_out;
430     }
431   }
432 
433 bail_out:
434   return jobid;
435 }
436 
437 /**
438  * Filter convenience functions that abstract the actions needed to
439  * perform a certain type of acl or resource filtering.
440  */
SetAclFilter(UaContext * ua,int column,int acltype)441 static inline void SetAclFilter(UaContext* ua, int column, int acltype)
442 {
443   if (ua->AclHasRestrictions(acltype)) {
444     ua->send->AddAclFilterTuple(column, acltype);
445   }
446 }
447 
SetResFilter(UaContext * ua,int column,int restype)448 static inline void SetResFilter(UaContext* ua, int column, int restype)
449 {
450   ua->send->AddResFilterTuple(column, restype);
451 }
452 
SetEnabledFilter(UaContext * ua,int column,int restype)453 static inline void SetEnabledFilter(UaContext* ua, int column, int restype)
454 {
455   ua->send->AddEnabledFilterTuple(column, restype);
456 }
457 
SetDisabledFilter(UaContext * ua,int column,int restype)458 static inline void SetDisabledFilter(UaContext* ua, int column, int restype)
459 {
460   ua->send->AddDisabledFilterTuple(column, restype);
461 }
462 
SetHiddenColumnAclFilter(UaContext * ua,int column,int acltype)463 static inline void SetHiddenColumnAclFilter(UaContext* ua,
464                                             int column,
465                                             int acltype)
466 {
467   ua->send->AddHiddenColumn(column);
468   if (ua->AclHasRestrictions(acltype)) {
469     ua->send->AddAclFilterTuple(column, acltype);
470   }
471 }
472 
SetHiddenColumn(UaContext * ua,int column)473 static inline void SetHiddenColumn(UaContext* ua, int column)
474 {
475   ua->send->AddHiddenColumn(column);
476 }
477 
SetQueryRange(PoolMem & query_range,UaContext * ua,JobDbRecord * jr)478 static void SetQueryRange(PoolMem& query_range, UaContext* ua, JobDbRecord* jr)
479 {
480   int i;
481 
482   /*
483    * Ensure query range is an empty string instead of NULL
484    * to avoid any issues.
485    */
486   if (query_range.c_str() == NULL) { PmStrcpy(query_range, ""); }
487 
488   /*
489    * See if this is a second call to SetQueryRange() if so and any acl
490    * filters have been set we setup a new query_range filter including a
491    * limit filter.
492    */
493   if (query_range.strlen()) {
494     if (!ua->send->has_acl_filters()) { return; }
495     PmStrcpy(query_range, "");
496   }
497 
498   /*
499    * Apply any limit
500    */
501   i = FindArgWithValue(ua, NT_("limit"));
502   if (i >= 0) {
503     PoolMem temp(PM_MESSAGE);
504 
505     jr->limit = atoi(ua->argv[i]);
506     ua->send->AddLimitFilterTuple(jr->limit);
507 
508     temp.bsprintf(" LIMIT %d", jr->limit);
509     PmStrcat(query_range, temp.c_str());
510 
511     /*
512      * offset is only valid, if limit is given
513      */
514     i = FindArgWithValue(ua, NT_("offset"));
515     if (i >= 0) {
516       jr->offset = atoi(ua->argv[i]);
517       ua->send->AddOffsetFilterTuple(jr->offset);
518       temp.bsprintf(" OFFSET %d", atoi(ua->argv[i]));
519       PmStrcat(query_range, temp.c_str());
520     }
521   }
522 }
523 
DoListCmd(UaContext * ua,const char * cmd,e_list_type llist)524 static bool DoListCmd(UaContext* ua, const char* cmd, e_list_type llist)
525 {
526   JobDbRecord jr;
527   PoolDbRecord pr;
528   utime_t now;
529   MediaDbRecord mr;
530   int days = 0, hours = 0, jobstatus = 0, joblevel = 0;
531   bool count, last, current, enabled, disabled;
532   int i, d, h, jobid;
533   time_t schedtime = 0;
534   char* clientname = NULL;
535   char* volumename = NULL;
536   char* poolname = NULL;
537   const int secs_in_day = 86400;
538   const int secs_in_hour = 3600;
539   PoolMem query_range(PM_MESSAGE);
540 
541   if (!OpenClientDb(ua, true)) { return true; }
542 
543   Dmsg1(20, "list: %s\n", cmd);
544 
545   if (ua->argc <= 1) {
546     ua->ErrorMsg(_("%s command requires a keyword\n"), NPRT(ua->argk[0]));
547     return false;
548   }
549 
550   /*
551    * days or hours given?
552    */
553   d = FindArgWithValue(ua, NT_("days"));
554   h = FindArgWithValue(ua, NT_("hours"));
555 
556   now = (utime_t)time(NULL);
557   if (d > 0) {
558     days = str_to_int64(ua->argv[d]);
559     schedtime = now - secs_in_day * days; /* Days in the past */
560   }
561 
562   if (h > 0) {
563     hours = str_to_int64(ua->argv[h]);
564     schedtime = now - secs_in_hour * hours; /* Hours in the past */
565   }
566 
567   current = FindArg(ua, NT_("current")) >= 0;
568   enabled = FindArg(ua, NT_("enabled")) >= 0;
569   disabled = FindArg(ua, NT_("disabled")) >= 0;
570   count = FindArg(ua, NT_("count")) >= 0;
571   last = FindArg(ua, NT_("last")) >= 0;
572 
573   PmStrcpy(query_range, "");
574   SetQueryRange(query_range, ua, &jr);
575 
576   i = FindArgWithValue(ua, NT_("client"));
577   if (i >= 0) {
578     if (ua->GetClientResWithName(ua->argv[i])) {
579       clientname = ua->argv[i];
580     } else {
581       ua->ErrorMsg(_("invalid client parameter\n"));
582       return false;
583     }
584   }
585 
586   /*
587    * jobstatus=X
588    */
589   if (!GetUserJobStatusSelection(ua, &jobstatus)) {
590     ua->ErrorMsg(_("invalid jobstatus parameter\n"));
591     return false;
592   }
593 
594   /*
595    * joblevel=X
596    */
597   if (!GetUserJobLevelSelection(ua, &joblevel)) {
598     ua->ErrorMsg(_("invalid joblevel parameter\n"));
599     return false;
600   }
601 
602   /*
603    * Select what to do based on the first argument.
604    */
605   if ((Bstrcasecmp(ua->argk[1], NT_("jobs")) && (ua->argv[1] == NULL)) ||
606       ((Bstrcasecmp(ua->argk[1], NT_("job")) ||
607         Bstrcasecmp(ua->argk[1], NT_("jobname"))) &&
608        ua->argv[1])) {
609     /*
610      * List jobs or List job=xxx
611      */
612     i = FindArgWithValue(ua, NT_("jobname"));
613     if (i < 0) { i = FindArgWithValue(ua, NT_("job")); }
614     if (i >= 0) {
615       jr.JobId = 0;
616       bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
617     }
618 
619     i = FindArgWithValue(ua, NT_("volume"));
620     if (i >= 0) { volumename = ua->argv[i]; }
621 
622     i = FindArgWithValue(ua, NT_("pool"));
623     if (i >= 0) { poolname = ua->argv[i]; }
624 
625     switch (llist) {
626       case VERT_LIST:
627         if (!count) {
628           SetAclFilter(ua, 2, Job_ACL);      /* JobName */
629           SetAclFilter(ua, 7, Client_ACL);   /* ClientName */
630           SetAclFilter(ua, 21, Pool_ACL);    /* PoolName */
631           SetAclFilter(ua, 24, FileSet_ACL); /* FilesetName */
632         }
633         if (current) {
634           SetResFilter(ua, 2, R_JOB);      /* JobName */
635           SetResFilter(ua, 7, R_CLIENT);   /* ClientName */
636           SetResFilter(ua, 21, R_POOL);    /* PoolName */
637           SetResFilter(ua, 24, R_FILESET); /* FilesetName */
638         }
639         if (enabled) { SetEnabledFilter(ua, 2, R_JOB); /* JobName */ }
640         if (disabled) { SetDisabledFilter(ua, 2, R_JOB); /* JobName */ }
641         break;
642       default:
643         if (!count) {
644           SetAclFilter(ua, 1, Job_ACL);    /* JobName */
645           SetAclFilter(ua, 2, Client_ACL); /* ClientName */
646         }
647         if (current) {
648           SetResFilter(ua, 1, R_JOB);    /* JobName */
649           SetResFilter(ua, 2, R_CLIENT); /* ClientName */
650         }
651         if (enabled) { SetEnabledFilter(ua, 1, R_JOB); /* JobName */ }
652         if (disabled) { SetDisabledFilter(ua, 1, R_JOB); /* JobName */ }
653         break;
654     }
655 
656     SetQueryRange(query_range, ua, &jr);
657 
658     ua->db->ListJobRecords(ua->jcr, &jr, query_range.c_str(), clientname,
659                            jobstatus, joblevel, volumename, poolname, schedtime,
660                            last, count, ua->send, llist);
661   } else if (Bstrcasecmp(ua->argk[1], NT_("jobtotals"))) {
662     /*
663      * List JOBTOTALS
664      */
665     ua->db->ListJobTotals(ua->jcr, &jr, ua->send);
666   } else if ((Bstrcasecmp(ua->argk[1], NT_("jobid")) ||
667               Bstrcasecmp(ua->argk[1], NT_("ujobid"))) &&
668              ua->argv[1]) {
669     /*
670      * List JOBID=nn
671      * List UJOBID=xxx
672      */
673     if (ua->argv[1]) {
674       jobid = GetJobidFromCmdline(ua);
675       if (jobid > 0) {
676         jr.JobId = jobid;
677 
678         i = FindArgWithValue(ua, NT_("pool"));
679         if (i >= 0) { poolname = ua->argv[i]; }
680 
681         switch (llist) {
682           case VERT_LIST:
683             SetAclFilter(ua, 2, Job_ACL);      /* JobName */
684             SetAclFilter(ua, 7, Client_ACL);   /* ClientName */
685             SetAclFilter(ua, 21, Pool_ACL);    /* PoolName */
686             SetAclFilter(ua, 24, FileSet_ACL); /* FilesetName */
687             if (current) {
688               SetResFilter(ua, 2, R_JOB);      /* JobName */
689               SetResFilter(ua, 7, R_CLIENT);   /* ClientName */
690               SetResFilter(ua, 21, R_POOL);    /* PoolName */
691               SetResFilter(ua, 24, R_FILESET); /* FilesetName */
692             }
693             if (enabled) { SetEnabledFilter(ua, 2, R_JOB); /* JobName */ }
694             if (disabled) { SetDisabledFilter(ua, 2, R_JOB); /* JobName */ }
695             break;
696           default:
697             SetAclFilter(ua, 1, Job_ACL);    /* JobName */
698             SetAclFilter(ua, 2, Client_ACL); /* ClientName */
699             if (current) {
700               SetResFilter(ua, 1, R_JOB);    /* JobName */
701               SetResFilter(ua, 2, R_CLIENT); /* ClientName */
702             }
703             if (enabled) { SetEnabledFilter(ua, 1, R_JOB); /* JobName */ }
704             if (disabled) { SetDisabledFilter(ua, 1, R_JOB); /* JobName */ }
705             break;
706         }
707 
708         SetQueryRange(query_range, ua, &jr);
709 
710         ua->db->ListJobRecords(ua->jcr, &jr, query_range.c_str(), clientname,
711                                jobstatus, joblevel, volumename, poolname,
712                                schedtime, last, count, ua->send, llist);
713       }
714     }
715   } else if (Bstrcasecmp(ua->argk[1], NT_("basefiles"))) {
716     /*
717      * List BASEFILES
718      */
719     jobid = GetJobidFromCmdline(ua);
720     if (jobid > 0) {
721       ua->db->ListBaseFilesForJob(ua->jcr, jobid, ua->send);
722     } else {
723       ua->ErrorMsg(
724           _("jobid not found in db, access to job or client denied by ACL, or "
725             "client not found in db\n"));
726     }
727   } else if (Bstrcasecmp(ua->argk[1], NT_("files"))) {
728     /*
729      * List FILES
730      */
731     jobid = GetJobidFromCmdline(ua);
732     if (jobid > 0) {
733       ua->db->ListFilesForJob(ua->jcr, jobid, ua->send);
734     } else {
735       ua->ErrorMsg(
736           _("jobid not found in db, access to job or client denied by ACL, or "
737             "client not found in db\n"));
738     }
739   } else if (Bstrcasecmp(ua->argk[1], NT_("fileset"))) {
740     int filesetid = 0;
741 
742     /*
743      * List FileSet
744      */
745     i = FindArgWithValue(ua, NT_("filesetid"));
746     if (i > 0) { filesetid = str_to_int64(ua->argv[i]); }
747 
748     jobid = GetJobidFromCmdline(ua);
749     if (jobid > 0 || filesetid > 0) {
750       jr.JobId = jobid;
751       jr.FileSetId = filesetid;
752 
753       SetAclFilter(ua, 1, FileSet_ACL); /* FilesetName */
754       if (current) { SetResFilter(ua, 1, R_FILESET); /* FilesetName */ }
755 
756       SetQueryRange(query_range, ua, &jr);
757 
758       ua->db->ListFilesets(ua->jcr, &jr, query_range.c_str(), ua->send, llist);
759     } else {
760       ua->ErrorMsg(
761           _("jobid not found in db, access to job or client denied by ACL, or "
762             "client not found in db or missing filesetid\n"));
763     }
764   } else if (Bstrcasecmp(ua->argk[1], NT_("filesets"))) {
765     /*
766      * List FILESETs
767      */
768     SetAclFilter(ua, 1, FileSet_ACL); /* FilesetName */
769     if (current) { SetResFilter(ua, 1, R_FILESET); /* FilesetName */ }
770 
771     SetQueryRange(query_range, ua, &jr);
772 
773     ua->db->ListFilesets(ua->jcr, &jr, query_range.c_str(), ua->send, llist);
774   } else if (Bstrcasecmp(ua->argk[1], NT_("jobmedia"))) {
775     /*
776      * List JOBMEDIA
777      */
778     jobid = GetJobidFromCmdline(ua);
779     if (jobid >= 0) {
780       ua->db->ListJobmediaRecords(ua->jcr, jobid, ua->send, llist);
781     } else {
782       ua->ErrorMsg(
783           _("jobid not found in db, access to job or client denied by ACL, or "
784             "client not found in db\n"));
785     }
786   } else if (Bstrcasecmp(ua->argk[1], NT_("joblog"))) {
787     /*
788      * List JOBLOG
789      */
790     jobid = GetJobidFromCmdline(ua);
791     if (jobid >= 0) {
792       ua->db->ListJoblogRecords(ua->jcr, jobid, query_range.c_str(), count,
793                                 ua->send, llist);
794     } else {
795       ua->ErrorMsg(
796           _("jobid not found in db, access to job or client denied by ACL, or "
797             "client not found in db\n"));
798     }
799   } else if (Bstrcasecmp(ua->argk[1], NT_("log"))) {
800     bool reverse;
801 
802     /*
803      * List last <limit> LOG entries
804      * default is DEFAULT_LOG_LINES entries
805      */
806     reverse = FindArg(ua, NT_("reverse")) >= 0;
807 
808     if (strlen(query_range.c_str()) == 0) {
809       Mmsg(query_range, " LIMIT %d", DEFAULT_LOG_LINES);
810     }
811 
812     if (ua->api != API_MODE_JSON) {
813       SetHiddenColumn(ua, 0);                      /* LogId */
814       SetHiddenColumnAclFilter(ua, 1, Job_ACL);    /* JobName */
815       SetHiddenColumnAclFilter(ua, 2, Client_ACL); /* ClientName */
816       SetHiddenColumn(ua, 3);                      /* LogTime */
817     } else {
818       SetAclFilter(ua, 1, Job_ACL);    /* JobName */
819       SetAclFilter(ua, 2, Client_ACL); /* ClientName */
820     }
821 
822     SetQueryRange(query_range, ua, &jr);
823 
824     ua->db->ListLogRecords(ua->jcr, clientname, query_range.c_str(), reverse,
825                            ua->send, llist);
826   } else if (Bstrcasecmp(ua->argk[1], NT_("pool")) ||
827              Bstrcasecmp(ua->argk[1], NT_("pools"))) {
828     PoolDbRecord pr;
829 
830     /*
831      * List POOLS
832      */
833     if (ua->argv[1]) { bstrncpy(pr.Name, ua->argv[1], sizeof(pr.Name)); }
834 
835     SetAclFilter(ua, 1, Pool_ACL); /* PoolName */
836     if (current) { SetResFilter(ua, 1, R_POOL); /* PoolName */ }
837 
838     SetQueryRange(query_range, ua, &jr);
839 
840     ua->db->ListPoolRecords(ua->jcr, &pr, ua->send, llist);
841   } else if (Bstrcasecmp(ua->argk[1], NT_("clients"))) {
842     /*
843      * List CLIENTS
844      */
845     SetAclFilter(ua, 1, Client_ACL); /* ClientName */
846     if (current) { SetResFilter(ua, 1, R_CLIENT); /* ClientName */ }
847     if (enabled) { SetEnabledFilter(ua, 1, R_CLIENT); /* ClientName */ }
848     if (disabled) { SetDisabledFilter(ua, 1, R_CLIENT); /* ClientName */ }
849 
850     SetQueryRange(query_range, ua, &jr);
851 
852     ua->db->ListClientRecords(ua->jcr, NULL, ua->send, llist);
853   } else if (Bstrcasecmp(ua->argk[1], NT_("client")) && ua->argv[1]) {
854     /*
855      * List CLIENT=xxx
856      */
857     SetAclFilter(ua, 1, Client_ACL); /* ClientName */
858     if (current) { SetResFilter(ua, 1, R_CLIENT); /* ClientName */ }
859     if (enabled) { SetEnabledFilter(ua, 1, R_CLIENT); /* ClientName */ }
860     if (disabled) { SetDisabledFilter(ua, 1, R_CLIENT); /* ClientName */ }
861 
862     SetQueryRange(query_range, ua, &jr);
863 
864     ua->db->ListClientRecords(ua->jcr, ua->argv[1], ua->send, llist);
865   } else if (Bstrcasecmp(ua->argk[1], NT_("storages"))) {
866     /*
867      * List STORAGES
868      */
869     SetAclFilter(ua, 1, Storage_ACL); /* StorageName */
870     if (current) { SetResFilter(ua, 1, R_STORAGE); /* StorageName */ }
871     if (enabled) { SetEnabledFilter(ua, 1, R_STORAGE); /* StorageName */ }
872     if (disabled) { SetDisabledFilter(ua, 1, R_STORAGE); /* StorageName */ }
873 
874     SetQueryRange(query_range, ua, &jr);
875 
876     ua->db->ListSqlQuery(ua->jcr, "SELECT * FROM Storage", ua->send, llist,
877                          "storages");
878   } else if (Bstrcasecmp(ua->argk[1], NT_("media")) ||
879              Bstrcasecmp(ua->argk[1], NT_("volume")) ||
880              Bstrcasecmp(ua->argk[1], NT_("volumes"))) {
881     /*
882      * List MEDIA or VOLUMES
883      */
884     jobid = GetJobidFromCmdline(ua);
885     if (jobid > 0) {
886       ua->db->ListVolumesOfJobid(ua->jcr, jobid, ua->send, llist);
887     } else if (jobid == 0) {
888       /*
889        * List a specific volume?
890        */
891       if (ua->argv[1]) {
892         bstrncpy(mr.VolumeName, ua->argv[1], sizeof(mr.VolumeName));
893         ua->send->ObjectStart("volume");
894         ua->db->ListMediaRecords(ua->jcr, &mr, query_range.c_str(), count,
895                                  ua->send, llist);
896         ua->send->ObjectEnd("volume");
897       } else {
898         /*
899          * If no job or jobid keyword found, then we list all media
900          * Is a specific pool wanted?
901          */
902         i = FindArgWithValue(ua, NT_("pool"));
903         if (i >= 0) {
904           bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
905 
906           if (!GetPoolDbr(ua, &pr)) {
907             ua->ErrorMsg(_("Pool %s doesn't exist.\n"), ua->argv[i]);
908             return true;
909           }
910 
911           SetQueryRange(query_range, ua, &jr);
912 
913           mr.PoolId = pr.PoolId;
914           ua->send->ArrayStart("volumes");
915           ua->db->ListMediaRecords(ua->jcr, &mr, query_range.c_str(), count,
916                                    ua->send, llist);
917           ua->send->ArrayEnd("volumes");
918           return true;
919         } else {
920           int num_pools;
921           uint32_t* ids = nullptr;
922 
923           /*
924            * List all volumes, flat
925            */
926           if (FindArg(ua, NT_("all")) > 0) {
927             /*
928              * The result of "list media all"
929              * does not contain the Pool information,
930              * therefore checking the Pool_ACL is not possible.
931              * For this reason, we prevent this command.
932              */
933             if (ua->AclHasRestrictions(Pool_ACL) && (llist != VERT_LIST)) {
934               ua->ErrorMsg(
935                   _("Restricted permission. Use the commands 'list media' or "
936                     "'llist media all' instead\n"));
937               return false;
938             }
939             ua->send->ArrayStart("volumes");
940             SetAclFilter(ua, 4, Pool_ACL); /* PoolName */
941             if (current) { SetResFilter(ua, 4, R_POOL); /* PoolName */ }
942             ua->db->ListMediaRecords(ua->jcr, &mr, query_range.c_str(), count,
943                                      ua->send, llist);
944             ua->send->ArrayEnd("volumes");
945           } else {
946             /*
947              * List Volumes in all pools
948              */
949             if (!ua->db->GetPoolIds(ua->jcr, &num_pools, &ids)) {
950               ua->ErrorMsg(_("Error obtaining pool ids. ERR=%s\n"),
951                            ua->db->strerror());
952               return true;
953             }
954 
955             if (num_pools <= 0) {
956               if (ids) { free(ids); }
957               return true;
958             }
959 
960             ua->send->ObjectStart("volumes");
961             for (i = 0; i < num_pools; i++) {
962               pr.PoolId = ids[i];
963               if (ua->db->GetPoolRecord(ua->jcr, &pr)) {
964                 if (ua->AclAccessOk(Pool_ACL, pr.Name, false)) {
965                   ua->send->Decoration("Pool: %s\n", pr.Name);
966                   ua->send->ArrayStart(pr.Name);
967                   mr.PoolId = ids[i];
968                   ua->db->ListMediaRecords(ua->jcr, &mr, query_range.c_str(),
969                                            count, ua->send, llist);
970                   ua->send->ArrayEnd(pr.Name);
971                 }
972               }
973             }
974             ua->send->ObjectEnd("volumes");
975             free(ids);
976           }
977         }
978       }
979     }
980 
981     return true;
982   } else if (Bstrcasecmp(ua->argk[1], NT_("nextvol")) ||
983              Bstrcasecmp(ua->argk[1], NT_("nextvolume"))) {
984     int days;
985 
986     /*
987      * List next volume
988      */
989     days = 1;
990 
991     i = FindArgWithValue(ua, NT_("days"));
992     if (i >= 0) {
993       days = atoi(ua->argv[i]);
994       if ((days < 0) || (days > DEFAULT_NR_DAYS)) {
995         ua->WarningMsg(_("Ignoring invalid value for days. Max is %d.\n"),
996                        DEFAULT_NR_DAYS);
997         days = 1;
998       }
999     }
1000     ListNextvol(ua, days);
1001   } else if (Bstrcasecmp(ua->argk[1], NT_("copies"))) {
1002     /*
1003      * List copies
1004      */
1005     i = FindArgWithValue(ua, NT_("jobid"));
1006     if (i >= 0) {
1007       if (Is_a_number_list(ua->argv[i])) {
1008         ua->db->ListCopiesRecords(ua->jcr, query_range.c_str(), ua->argv[i],
1009                                   ua->send, llist);
1010       }
1011     } else {
1012       ua->db->ListCopiesRecords(ua->jcr, query_range.c_str(), NULL, ua->send,
1013                                 llist);
1014     }
1015   } else if (Bstrcasecmp(ua->argk[1], NT_("backups"))) {
1016     if (ParseListBackupsCmd(ua, query_range.c_str(), llist)) {
1017       switch (llist) {
1018         case VERT_LIST:
1019           SetAclFilter(ua, 2, Job_ACL);   /* JobName */
1020           SetAclFilter(ua, 21, Pool_ACL); /* PoolName */
1021           if (current) {
1022             SetResFilter(ua, 2, R_JOB);   /* JobName */
1023             SetResFilter(ua, 21, R_POOL); /* PoolName */
1024           }
1025           if (enabled) { SetEnabledFilter(ua, 2, R_JOB); /* JobName */ }
1026           if (disabled) { SetDisabledFilter(ua, 2, R_JOB); /* JobName */ }
1027           break;
1028         default:
1029           SetAclFilter(ua, 1, Job_ACL); /* JobName */
1030           if (current) { SetResFilter(ua, 1, R_JOB); /* JobName */ }
1031           if (enabled) { SetEnabledFilter(ua, 1, R_JOB); /* JobName */ }
1032           if (disabled) { SetDisabledFilter(ua, 1, R_JOB); /* JobName */ }
1033           break;
1034       }
1035 
1036       ua->db->ListSqlQuery(ua->jcr, ua->cmd, ua->send, llist, "backups");
1037     }
1038   } else if (Bstrcasecmp(ua->argk[1], NT_("jobstatistics")) ||
1039              Bstrcasecmp(ua->argk[1], NT_("jobstats"))) {
1040     jobid = GetJobidFromCmdline(ua);
1041     if (jobid > 0) {
1042       ua->db->ListJobstatisticsRecords(ua->jcr, jobid, ua->send, llist);
1043     } else {
1044       ua->ErrorMsg(_("no jobid given\n"));
1045       return false;
1046     }
1047   } else {
1048     ua->ErrorMsg(_("Unknown list keyword: %s\n"), NPRT(ua->argk[1]));
1049     return false;
1050   }
1051 
1052   return true;
1053 }
1054 
parse_jobstatus_selection_param(PoolMem & selection,UaContext * ua,const char * default_selection)1055 static inline bool parse_jobstatus_selection_param(
1056     PoolMem& selection,
1057     UaContext* ua,
1058     const char* default_selection)
1059 {
1060   int pos;
1061 
1062   selection.strcpy("");
1063   if ((pos = FindArgWithValue(ua, "jobstatus")) >= 0) {
1064     int cnt = 0;
1065     int jobstatus;
1066     PoolMem temp;
1067     char *cur_stat, *bp;
1068 
1069     cur_stat = ua->argv[pos];
1070     while (cur_stat) {
1071       bp = strchr(cur_stat, ',');
1072       if (bp) { *bp++ = '\0'; }
1073 
1074       /*
1075        * Try matching the status to an internal Job Termination code.
1076        */
1077       if (strlen(cur_stat) == 1 && cur_stat[0] >= 'A' && cur_stat[0] <= 'z') {
1078         jobstatus = cur_stat[0];
1079       } else if (Bstrcasecmp(cur_stat, "terminated")) {
1080         jobstatus = JS_Terminated;
1081       } else if (Bstrcasecmp(cur_stat, "warnings")) {
1082         jobstatus = JS_Warnings;
1083       } else if (Bstrcasecmp(cur_stat, "canceled")) {
1084         jobstatus = JS_Canceled;
1085       } else if (Bstrcasecmp(cur_stat, "running")) {
1086         jobstatus = JS_Running;
1087       } else if (Bstrcasecmp(cur_stat, "error")) {
1088         jobstatus = JS_Error;
1089       } else if (Bstrcasecmp(cur_stat, "fatal")) {
1090         jobstatus = JS_FatalError;
1091       } else {
1092         cur_stat = bp;
1093         continue;
1094       }
1095 
1096       if (cnt == 0) {
1097         Mmsg(temp, " AND JobStatus IN ('%c'", jobstatus);
1098         PmStrcat(selection, temp.c_str());
1099       } else {
1100         Mmsg(temp, ",'%c'", jobstatus);
1101         PmStrcat(selection, temp.c_str());
1102       }
1103       cur_stat = bp;
1104       cnt++;
1105     }
1106 
1107     /*
1108      * Close set if we opened one.
1109      */
1110     if (cnt > 0) { PmStrcat(selection, ")"); }
1111   }
1112 
1113   if (selection.strlen() == 0) {
1114     /*
1115      * When no explicit Job Termination code specified use default
1116      */
1117     selection.strcpy(default_selection);
1118   }
1119 
1120   return true;
1121 }
1122 
parse_level_selection_param(PoolMem & selection,UaContext * ua,const char * default_selection)1123 static inline bool parse_level_selection_param(PoolMem& selection,
1124                                                UaContext* ua,
1125                                                const char* default_selection)
1126 {
1127   int pos;
1128 
1129   selection.strcpy("");
1130   if ((pos = FindArgWithValue(ua, "level")) >= 0) {
1131     int cnt = 0;
1132     PoolMem temp;
1133     char *cur_level, *bp;
1134 
1135     cur_level = ua->argv[pos];
1136     while (cur_level) {
1137       bp = strchr(cur_level, ',');
1138       if (bp) { *bp++ = '\0'; }
1139 
1140       /*
1141        * Try mapping from text level to internal level.
1142        */
1143       for (int i = 0; joblevels[i].level_name; i++) {
1144         if (joblevels[i].job_type == JT_BACKUP &&
1145             bstrncasecmp(joblevels[i].level_name, cur_level,
1146                          strlen(cur_level))) {
1147           if (cnt == 0) {
1148             Mmsg(temp, " AND Level IN ('%c'", joblevels[i].level);
1149             PmStrcat(selection, temp.c_str());
1150           } else {
1151             Mmsg(temp, ",'%c'", joblevels[i].level);
1152             PmStrcat(selection, temp.c_str());
1153           }
1154         }
1155       }
1156       cur_level = bp;
1157       cnt++;
1158     }
1159 
1160     /*
1161      * Close set if we opened one.
1162      */
1163     if (cnt > 0) { PmStrcat(selection, ")"); }
1164   }
1165   if (selection.strlen() == 0) { selection.strcpy(default_selection); }
1166 
1167   return true;
1168 }
1169 
parse_fileset_selection_param(PoolMem & selection,UaContext * ua,bool listall)1170 static inline bool parse_fileset_selection_param(PoolMem& selection,
1171                                                  UaContext* ua,
1172                                                  bool listall)
1173 {
1174   int fileset;
1175 
1176   PmStrcpy(selection, "");
1177   fileset = FindArgWithValue(ua, "fileset");
1178   if ((fileset >= 0 && Bstrcasecmp(ua->argv[fileset], "any")) ||
1179       (listall && fileset < 0)) {
1180     FilesetResource* fs;
1181     PoolMem temp(PM_MESSAGE);
1182 
1183     LockRes(my_config);
1184     foreach_res (fs, R_FILESET) {
1185       if (!ua->AclAccessOk(FileSet_ACL, fs->resource_name_, false)) {
1186         continue;
1187       }
1188       if (selection.strlen() == 0) {
1189         temp.bsprintf("AND (FileSet='%s'", fs->resource_name_);
1190       } else {
1191         temp.bsprintf(" OR FileSet='%s'", fs->resource_name_);
1192       }
1193       PmStrcat(selection, temp.c_str());
1194     }
1195     PmStrcat(selection, ") ");
1196     UnlockRes(my_config);
1197   } else if (fileset >= 0) {
1198     if (!ua->AclAccessOk(FileSet_ACL, ua->argv[fileset], true)) {
1199       ua->ErrorMsg(_("Access to specified FileSet not allowed.\n"));
1200       return false;
1201     } else {
1202       selection.bsprintf("AND FileSet='%s' ", ua->argv[fileset]);
1203     }
1204   }
1205 
1206   return true;
1207 }
1208 
ParseListBackupsCmd(UaContext * ua,const char * range,e_list_type llist)1209 static bool ParseListBackupsCmd(UaContext* ua,
1210                                 const char* range,
1211                                 e_list_type llist)
1212 {
1213   int pos, client;
1214   PoolMem temp(PM_MESSAGE), selection(PM_MESSAGE), criteria(PM_MESSAGE);
1215 
1216   client = FindArgWithValue(ua, "client");
1217   if (client < 0) {
1218     ua->ErrorMsg(_("missing parameter: client\n"));
1219     return false;
1220   }
1221 
1222   if (!ua->AclAccessOk(Client_ACL, ua->argv[client], true)) {
1223     ua->ErrorMsg(_("Access to specified Client not allowed.\n"));
1224     return false;
1225   }
1226 
1227   selection.bsprintf("AND Job.Type='B' AND Client.Name='%s' ",
1228                      ua->argv[client]);
1229 
1230   /*
1231    * Build a selection pattern based on the jobstatus and level arguments.
1232    */
1233   parse_jobstatus_selection_param(temp, ua, "AND JobStatus IN ('T','W') ");
1234   PmStrcat(selection, temp.c_str());
1235 
1236   parse_level_selection_param(temp, ua, "");
1237   PmStrcat(selection, temp.c_str());
1238 
1239   if (!parse_fileset_selection_param(temp, ua, true)) { return false; }
1240   PmStrcat(selection, temp.c_str());
1241 
1242   /*
1243    * Build a criteria pattern if the order and/or limit argument are given.
1244    */
1245   PmStrcpy(criteria, "");
1246   if ((pos = FindArgWithValue(ua, "order")) >= 0) {
1247     if (bstrncasecmp(ua->argv[pos], "ascending", strlen(ua->argv[pos]))) {
1248       PmStrcat(criteria, " ASC");
1249     } else if (bstrncasecmp(ua->argv[pos], "descending",
1250                             strlen(ua->argv[pos]))) {
1251       PmStrcat(criteria, " DESC");
1252     } else {
1253       return false;
1254     }
1255   }
1256 
1257   /*
1258    * add range settings
1259    */
1260   PmStrcat(criteria, range);
1261 
1262   if (llist == VERT_LIST) {
1263     ua->db->FillQuery(ua->cmd, BareosDb::SQL_QUERY::list_jobs_long,
1264                       selection.c_str(), criteria.c_str());
1265   } else {
1266     ua->db->FillQuery(ua->cmd, BareosDb::SQL_QUERY::list_jobs,
1267                       selection.c_str(), criteria.c_str());
1268   }
1269 
1270   return true;
1271 }
1272 
ListNextvol(UaContext * ua,int ndays)1273 static bool ListNextvol(UaContext* ua, int ndays)
1274 {
1275   int i;
1276   JobResource* job;
1277   JobControlRecord* jcr;
1278   UnifiedStorageResource store;
1279   RunResource* run;
1280   utime_t runtime;
1281   bool found = false;
1282 
1283   i = FindArgWithValue(ua, "job");
1284   if (i <= 0) {
1285     if ((job = select_job_resource(ua)) == NULL) { return false; }
1286   } else {
1287     job = ua->GetJobResWithName(ua->argv[i]);
1288     if (!job) {
1289       Jmsg(ua->jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
1290       if ((job = select_job_resource(ua)) == NULL) { return false; }
1291     }
1292   }
1293 
1294   jcr = NewDirectorJcr();
1295   for (run = NULL; (run = find_next_run(run, job, runtime, ndays));) {
1296     if (!CompleteJcrForJob(jcr, job, run->pool)) {
1297       found = false;
1298       goto get_out;
1299     }
1300     if (!jcr->impl->jr.PoolId) {
1301       ua->ErrorMsg(_("Could not find Pool for Job %s\n"), job->resource_name_);
1302       continue;
1303     }
1304     PoolDbRecord pr;
1305     pr.PoolId = jcr->impl->jr.PoolId;
1306     if (!ua->db->GetPoolRecord(jcr, &pr)) {
1307       bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
1308     }
1309     MediaDbRecord mr;
1310     mr.PoolId = jcr->impl->jr.PoolId;
1311     GetJobStorage(&store, job, run);
1312     SetStorageidInMr(store.store, &mr);
1313     /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
1314     if (!FindNextVolumeForAppend(jcr, &mr, 1, NULL, fnv_no_create_vol,
1315                                  fnv_prune)) {
1316       ua->ErrorMsg(
1317           _("Could not find next Volume for Job %s (Pool=%s, Level=%s).\n"),
1318           job->resource_name_, pr.Name, JobLevelToString(run->level));
1319     } else {
1320       ua->SendMsg(_("The next Volume to be used by Job \"%s\" (Pool=%s, "
1321                     "Level=%s) will be %s\n"),
1322                   job->resource_name_, pr.Name, JobLevelToString(run->level),
1323                   mr.VolumeName);
1324       found = true;
1325     }
1326   }
1327 
1328 get_out:
1329   if (jcr->db) {
1330     DbSqlClosePooledConnection(jcr, jcr->db);
1331     jcr->db = NULL;
1332   }
1333   FreeJcr(jcr);
1334   if (!found) {
1335     ua->ErrorMsg(_("Could not find next Volume for Job %s.\n"),
1336                  job->resource_name_);
1337     return false;
1338   }
1339   return true;
1340 }
1341 
1342 /**
1343  * For a given job, we examine all his run records
1344  *  to see if it is scheduled today or tomorrow.
1345  */
find_next_run(RunResource * run,JobResource * job,utime_t & runtime,int ndays)1346 RunResource* find_next_run(RunResource* run,
1347                            JobResource* job,
1348                            utime_t& runtime,
1349                            int ndays)
1350 {
1351   time_t now, future, endtime;
1352   ScheduleResource* sched;
1353   struct tm tm, runtm;
1354   int mday, wday, month, wom, i;
1355   int woy;
1356   int day;
1357 
1358   sched = job->schedule;
1359   if (sched == NULL) { /* scheduled? */
1360     return NULL;       /* no nothing to report */
1361   }
1362 
1363   /* Break down the time into components */
1364   now = time(NULL);
1365   endtime = now + (ndays * 60 * 60 * 24);
1366 
1367   if (run == NULL) {
1368     run = sched->run;
1369   } else {
1370     run = run->next;
1371   }
1372   for (; run; run = run->next) {
1373     /*
1374      * Find runs in next 24 hours.  Day 0 is today, so if
1375      *   ndays=1, look at today and tomorrow.
1376      */
1377     for (day = 0; day <= ndays; day++) {
1378       future = now + (day * 60 * 60 * 24);
1379 
1380       /* Break down the time into components */
1381       Blocaltime(&future, &tm);
1382       mday = tm.tm_mday - 1;
1383       wday = tm.tm_wday;
1384       month = tm.tm_mon;
1385       wom = mday / 7;
1386       woy = TmWoy(future);
1387 
1388       bool is_scheduled = BitIsSet(mday, run->date_time_bitfield.mday) &&
1389                           BitIsSet(wday, run->date_time_bitfield.wday) &&
1390                           BitIsSet(month, run->date_time_bitfield.month) &&
1391                           BitIsSet(wom, run->date_time_bitfield.wom) &&
1392                           BitIsSet(woy, run->date_time_bitfield.woy);
1393 
1394       if (is_scheduled) { /* Jobs scheduled on that day */
1395         /* find time (time_t) job is to be run */
1396         Blocaltime(&future, &runtm);
1397         for (i = 0; i < 24; i++) {
1398           if (BitIsSet(i, run->date_time_bitfield.hour)) {
1399             runtm.tm_hour = i;
1400             runtm.tm_min = run->minute;
1401             runtm.tm_sec = 0;
1402             runtime = mktime(&runtm);
1403             Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
1404             if ((runtime > now) && (runtime < endtime)) {
1405               Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
1406               return run; /* found it, return run resource */
1407             }
1408           }
1409         }
1410       }
1411     }
1412   } /* end for loop over runs */
1413   /* Nothing found */
1414   return NULL;
1415 }
1416 
1417 /**
1418  * Fill in the remaining fields of the jcr as if it is going to run the job.
1419  */
CompleteJcrForJob(JobControlRecord * jcr,JobResource * job,PoolResource * pool)1420 bool CompleteJcrForJob(JobControlRecord* jcr,
1421                        JobResource* job,
1422                        PoolResource* pool)
1423 {
1424   SetJcrDefaults(jcr, job);
1425   if (pool) { jcr->impl->res.pool = pool; /* override */ }
1426   if (jcr->db) {
1427     Dmsg0(100, "complete_jcr close db\n");
1428     DbSqlClosePooledConnection(jcr, jcr->db);
1429     jcr->db = NULL;
1430   }
1431 
1432   Dmsg0(100, "complete_jcr open db\n");
1433   jcr->db = GetDatabaseConnection(jcr);
1434   if (jcr->db == NULL) {
1435     Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
1436          jcr->impl->res.catalog->db_name);
1437     return false;
1438   }
1439   PoolDbRecord pr;
1440   bstrncpy(pr.Name, jcr->impl->res.pool->resource_name_, sizeof(pr.Name));
1441   while (!jcr->db->GetPoolRecord(jcr, &pr)) { /* get by Name */
1442     /* Try to create the pool */
1443     if (CreatePool(jcr, jcr->db, jcr->impl->res.pool, POOL_OP_CREATE) < 0) {
1444       Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
1445            jcr->db->strerror());
1446       if (jcr->db) {
1447         DbSqlClosePooledConnection(jcr, jcr->db);
1448         jcr->db = NULL;
1449       }
1450       return false;
1451     } else {
1452       Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
1453     }
1454   }
1455   jcr->impl->jr.PoolId = pr.PoolId;
1456   return true;
1457 }
1458 
ConLockRelease(void * arg)1459 static void ConLockRelease(void* arg) { Vw(con_lock); }
1460 
DoMessages(UaContext * ua,const char * cmd)1461 void DoMessages(UaContext* ua, const char* cmd)
1462 {
1463   char msg[2000];
1464   int mlen;
1465   bool DoTruncate = false;
1466 
1467   /*
1468    * Flush any queued messages.
1469    */
1470   if (ua->jcr) { DequeueMessages(ua->jcr); }
1471 
1472   Pw(con_lock);
1473   pthread_cleanup_push(ConLockRelease, (void*)NULL);
1474   rewind(con_fd);
1475   while (fgets(msg, sizeof(msg), con_fd)) {
1476     mlen = strlen(msg);
1477     ua->UA_sock->msg = CheckPoolMemorySize(ua->UA_sock->msg, mlen + 1);
1478     strcpy(ua->UA_sock->msg, msg);
1479     ua->UA_sock->message_length = mlen;
1480     ua->UA_sock->send();
1481     DoTruncate = true;
1482   }
1483   if (DoTruncate) { (void)ftruncate(fileno(con_fd), 0L); }
1484   console_msg_pending = FALSE;
1485   ua->user_notified_msg_pending = FALSE;
1486   pthread_cleanup_pop(0);
1487   Vw(con_lock);
1488 }
1489 
DotMessagesCmd(UaContext * ua,const char * cmd)1490 bool DotMessagesCmd(UaContext* ua, const char* cmd)
1491 {
1492   if (console_msg_pending && ua->AclNoRestrictions(Command_ACL) &&
1493       ua->auto_display_messages) {
1494     DoMessages(ua, cmd);
1495   }
1496   return true;
1497 }
1498 
MessagesCmd(UaContext * ua,const char * cmd)1499 bool MessagesCmd(UaContext* ua, const char* cmd)
1500 {
1501   if (console_msg_pending && ua->AclNoRestrictions(Command_ACL)) {
1502     DoMessages(ua, cmd);
1503   } else {
1504     ua->send->Decoration(_("You have no messages.\n"));
1505   }
1506   return true;
1507 }
1508 
1509 /**
1510  * Callback routine for "filtering" database listing.
1511  */
filterit(void * ctx,void * data,of_filter_tuple * tuple)1512 of_filter_state filterit(void* ctx, void* data, of_filter_tuple* tuple)
1513 {
1514   char** row = (char**)data;
1515   UaContext* ua = (UaContext*)ctx;
1516   of_filter_state retval = OF_FILTER_STATE_SHOW;
1517 
1518   switch (tuple->type) {
1519     case OF_FILTER_LIMIT:
1520       break;
1521     case OF_FILTER_OFFSET:
1522       break;
1523     case OF_FILTER_ACL:
1524       if (!row[tuple->u.acl_filter.column] ||
1525           strlen(row[tuple->u.acl_filter.column]) == 0) {
1526         retval = OF_FILTER_STATE_UNKNOWN;
1527       } else {
1528         if (!ua->AclAccessOk(tuple->u.acl_filter.acltype,
1529                              row[tuple->u.acl_filter.column], false)) {
1530           Dmsg2(200,
1531                 "filterit: Filter on acl_type %d value %s, suppress output\n",
1532                 tuple->u.acl_filter.acltype, row[tuple->u.acl_filter.column]);
1533           retval = OF_FILTER_STATE_SUPPRESS;
1534         }
1535       }
1536       goto bail_out;
1537     case OF_FILTER_RESOURCE:
1538       if (!row[tuple->u.res_filter.column] ||
1539           strlen(row[tuple->u.res_filter.column]) == 0) {
1540         retval = OF_FILTER_STATE_UNKNOWN;
1541       } else {
1542         if (!my_config->GetResWithName(tuple->u.res_filter.restype,
1543                                        row[tuple->u.res_filter.column],
1544                                        false)) {
1545           Dmsg2(200,
1546                 "filterit: Filter on resource_type %d value %s, suppress "
1547                 "output\n",
1548                 tuple->u.res_filter.restype, row[tuple->u.res_filter.column]);
1549           retval = OF_FILTER_STATE_SUPPRESS;
1550         }
1551       }
1552       goto bail_out;
1553     case OF_FILTER_ENABLED:
1554     case OF_FILTER_DISABLED: {
1555       bool enabled = true;
1556 
1557       if (!row[tuple->u.res_filter.column] ||
1558           strlen(row[tuple->u.res_filter.column]) == 0) {
1559         retval = OF_FILTER_STATE_UNKNOWN;
1560         goto bail_out;
1561       }
1562 
1563       if (tuple->type == OF_FILTER_DISABLED) { enabled = false; }
1564 
1565       switch (tuple->u.res_filter.restype) {
1566         case R_CLIENT: {
1567           ClientResource* client;
1568 
1569           client = ua->GetClientResWithName(row[tuple->u.res_filter.column],
1570                                             false, false);
1571           if (!client || client->enabled != enabled) {
1572             Dmsg2(200, "filterit: Filter on Client, %s is not %sabled\n",
1573                   row[tuple->u.res_filter.column], (enabled) ? "En" : "Dis");
1574             retval = OF_FILTER_STATE_SUPPRESS;
1575           }
1576           goto bail_out;
1577         }
1578         case R_JOB: {
1579           JobResource* job;
1580 
1581           job = ua->GetJobResWithName(row[tuple->u.res_filter.column], false,
1582                                       false);
1583           if (!job || job->enabled != enabled) {
1584             Dmsg2(200, "filterit: Filter on Job, %s is not %sabled\n",
1585                   row[tuple->u.res_filter.column], (enabled) ? "En" : "Dis");
1586             retval = OF_FILTER_STATE_SUPPRESS;
1587           }
1588           goto bail_out;
1589         }
1590         case R_STORAGE: {
1591           StorageResource* store;
1592 
1593           store = ua->GetStoreResWithName(row[tuple->u.res_filter.column],
1594                                           false, false);
1595           if (!store || store->enabled != enabled) {
1596             Dmsg2(200, "filterit: Filter on Storage, %s is not %sabled\n",
1597                   row[tuple->u.res_filter.column], (enabled) ? "En" : "Dis");
1598             retval = OF_FILTER_STATE_SUPPRESS;
1599           }
1600           goto bail_out;
1601         }
1602         case R_SCHEDULE: {
1603           ScheduleResource* schedule;
1604 
1605           schedule = ua->GetScheduleResWithName(row[tuple->u.res_filter.column],
1606                                                 false, false);
1607           if (!schedule || schedule->enabled != enabled) {
1608             Dmsg2(200, "filterit: Filter on Schedule, %s is not %sabled\n",
1609                   row[tuple->u.res_filter.column], (enabled) ? "En" : "Dis");
1610             retval = OF_FILTER_STATE_SUPPRESS;
1611           }
1612           goto bail_out;
1613         }
1614         default:
1615           goto bail_out;
1616       }
1617       break;
1618     }
1619     default:
1620       retval = OF_FILTER_STATE_SUPPRESS;
1621   }
1622 
1623 bail_out:
1624   return retval;
1625 }
1626 
1627 /**
1628  * Callback routine for "printing" database listing
1629  */
printit(void * ctx,const char * msg)1630 bool printit(void* ctx, const char* msg)
1631 {
1632   bool retval = false;
1633   UaContext* ua = (UaContext*)ctx;
1634 
1635   if (ua->UA_sock) {
1636     retval = ua->UA_sock->fsend("%s", msg);
1637   } else { /* No UA, send to Job */
1638     Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
1639     retval = true;
1640   }
1641 
1642   return retval;
1643 }
1644 
1645 /**
1646  * Format message and send to other end.
1647 
1648  * If the UA_sock is NULL, it means that there is no user
1649  * agent, so we are being called from BAREOS core. In
1650  * that case direct the messages to the Job.
1651  */
1652 #ifdef HAVE_VA_COPY
bmsg(UaContext * ua,const char * fmt,va_list arg_ptr)1653 void bmsg(UaContext* ua, const char* fmt, va_list arg_ptr)
1654 {
1655   BareosSocket* bs = ua->UA_sock;
1656   int maxlen, len;
1657   POOLMEM* msg = NULL;
1658   va_list ap;
1659 
1660   if (bs) { msg = bs->msg; }
1661   if (!msg) { msg = GetPoolMemory(PM_EMSG); }
1662 
1663 again:
1664   maxlen = SizeofPoolMemory(msg) - 1;
1665   va_copy(ap, arg_ptr);
1666   len = Bvsnprintf(msg, maxlen, fmt, ap);
1667   va_end(ap);
1668   if (len < 0 || len >= maxlen) {
1669     msg = ReallocPoolMemory(msg, maxlen + maxlen / 2);
1670     goto again;
1671   }
1672 
1673   if (bs) {
1674     bs->msg = msg;
1675     bs->message_length = len;
1676     bs->send();
1677   } else { /* No UA, send to Job */
1678     Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
1679     FreePoolMemory(msg);
1680   }
1681 }
1682 
1683 #else /* no va_copy() -- brain damaged version of variable arguments */
1684 
bmsg(UaContext * ua,const char * fmt,va_list arg_ptr)1685 void bmsg(UaContext* ua, const char* fmt, va_list arg_ptr)
1686 {
1687   BareosSocket* bs = ua->UA_sock;
1688   int maxlen, len;
1689   POOLMEM* msg = NULL;
1690 
1691   if (bs) { msg = bs->msg; }
1692   if (!msg) { msg = GetMemory(5000); }
1693 
1694   maxlen = SizeofPoolMemory(msg) - 1;
1695   if (maxlen < 4999) {
1696     msg = ReallocPoolMemory(msg, 5000);
1697     maxlen = 4999;
1698   }
1699   len = Bvsnprintf(msg, maxlen, fmt, arg_ptr);
1700   if (len < 0 || len >= maxlen) {
1701     PmStrcpy(msg, _("Message too long to display.\n"));
1702     len = strlen(msg);
1703   }
1704 
1705   if (bs) {
1706     bs->msg = msg;
1707     bs->message_length = len;
1708     bs->send();
1709   } else { /* No UA, send to Job */
1710     Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
1711     FreePoolMemory(msg);
1712   }
1713 }
1714 #endif
1715 
bsendmsg(void * ctx,const char * fmt,...)1716 void bsendmsg(void* ctx, const char* fmt, ...)
1717 {
1718   va_list arg_ptr;
1719   va_start(arg_ptr, fmt);
1720   bmsg((UaContext*)ctx, fmt, arg_ptr);
1721   va_end(arg_ptr);
1722 }
1723 
1724 /*
1725  * The following UA methods are mainly intended for GUI
1726  * programs
1727  */
1728 /**
1729  * This is a message that should be displayed on the user's
1730  *  console.
1731  */
SendMsg(const char * fmt,...)1732 void UaContext::SendMsg(const char* fmt, ...)
1733 {
1734   va_list arg_ptr;
1735   PoolMem message;
1736 
1737   /* send current buffer */
1738   send->SendBuffer();
1739 
1740   va_start(arg_ptr, fmt);
1741   message.Bvsprintf(fmt, arg_ptr);
1742   va_end(arg_ptr);
1743   send->message(NULL, message);
1744 }
1745 
SendRawMsg(const char * msg)1746 void UaContext::SendRawMsg(const char* msg) { SendMsg(msg); }
1747 
1748 
1749 /**
1750  * This is an error condition with a command. The gui should put
1751  *  up an error or critical dialog box.  The command is aborted.
1752  */
ErrorMsg(const char * fmt,...)1753 void UaContext::ErrorMsg(const char* fmt, ...)
1754 {
1755   va_list arg_ptr;
1756   BareosSocket* bs = UA_sock;
1757   PoolMem message;
1758 
1759   /* send current buffer */
1760   send->SendBuffer();
1761 
1762   if (bs && api) bs->signal(BNET_ERROR_MSG);
1763   va_start(arg_ptr, fmt);
1764   message.Bvsprintf(fmt, arg_ptr);
1765   va_end(arg_ptr);
1766   send->message(MSG_TYPE_ERROR, message);
1767 }
1768 
1769 /**
1770  * This is a warning message, that should bring up a warning
1771  *  dialog box on the GUI. The command is not aborted, but something
1772  *  went wrong.
1773  */
WarningMsg(const char * fmt,...)1774 void UaContext::WarningMsg(const char* fmt, ...)
1775 {
1776   va_list arg_ptr;
1777   BareosSocket* bs = UA_sock;
1778   PoolMem message;
1779 
1780   /* send current buffer */
1781   send->SendBuffer();
1782 
1783   if (bs && api) bs->signal(BNET_WARNING_MSG);
1784   va_start(arg_ptr, fmt);
1785   message.Bvsprintf(fmt, arg_ptr);
1786   va_end(arg_ptr);
1787   send->message(MSG_TYPE_WARNING, message);
1788 }
1789 
1790 /**
1791  * This is an information message that should probably be put
1792  *  into the status line of a GUI program.
1793  */
InfoMsg(const char * fmt,...)1794 void UaContext::InfoMsg(const char* fmt, ...)
1795 {
1796   va_list arg_ptr;
1797   BareosSocket* bs = UA_sock;
1798   PoolMem message;
1799 
1800   /* send current buffer */
1801   send->SendBuffer();
1802 
1803   if (bs && api) bs->signal(BNET_INFO_MSG);
1804   va_start(arg_ptr, fmt);
1805   message.Bvsprintf(fmt, arg_ptr);
1806   va_end(arg_ptr);
1807   send->message(MSG_TYPE_INFO, message);
1808 }
1809 
1810 
SendCmdUsage(const char * fmt,...)1811 void UaContext::SendCmdUsage(const char* fmt, ...)
1812 {
1813   va_list arg_ptr;
1814   PoolMem message;
1815   PoolMem usage;
1816 
1817   /* send current buffer */
1818   send->SendBuffer();
1819 
1820   va_start(arg_ptr, fmt);
1821   message.Bvsprintf(fmt, arg_ptr);
1822   va_end(arg_ptr);
1823 
1824   if (cmddef) {
1825     if (cmddef->key && cmddef->usage) {
1826       usage.bsprintf("\nUSAGE: %s %s\n", cmddef->key, cmddef->usage);
1827       message.strcat(usage);
1828     }
1829   }
1830 
1831   send->message(NULL, message);
1832 }
1833 } /* namespace directordaemon */
1834