1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2019-2019 Bareos GmbH & Co. KG
5 
6    This program is Free Software; you can redistribute it and/or
7    modify it under the terms of version three of the GNU Affero General Public
8    License as published by the Free Software Foundation and included
9    in the file LICENSE.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14    Affero General Public License for more details.
15 
16    You should have received a copy of the GNU Affero General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19    02110-1301, USA.
20 */
21 
22 #include "include/bareos.h"
23 #include "bnet_network_dump_private.h"
24 
25 #include "include/make_unique.h"
26 #include "lib/ascii_control_characters.h"
27 #include "lib/backtrace.h"
28 #include "lib/bareos_resource.h"
29 #include "lib/bnet.h"
30 #include "lib/bsock.h"
31 #include "lib/bsock_tcp.h"
32 #include "lib/bstringlist.h"
33 #include "lib/qualified_resource_name_type_converter.h"
34 
35 #include <cassert>
36 #include <algorithm>
37 #include <iostream>
38 #include <fstream>
39 #include <set>
40 
41 std::string BnetDumpPrivate::filename_;
42 bool BnetDumpPrivate::plantuml_mode_ = false;
43 std::size_t BnetDumpPrivate::max_data_dump_bytes_ = 100;
44 int BnetDumpPrivate::stack_level_start_ = 6;
45 int BnetDumpPrivate::stack_level_amount_ = 0;
46 std::set<std::string> BnetDumpPrivate::exclude_rcodes_;  //= {"R_CONSOLE"};
47 
OpenFile()48 void BnetDumpPrivate::OpenFile()
49 {
50   if (!filename_.empty()) {
51     output_file_.open(filename_, std::ios::app);
52     assert(output_file_.is_open());
53   }
54 }
55 
CloseFile()56 void BnetDumpPrivate::CloseFile() { output_file_.close(); }
57 
SetFilename(const char * filename)58 bool BnetDumpPrivate::SetFilename(const char* filename)
59 {
60   BnetDumpPrivate::filename_ = filename;
61   return true;
62 }
63 
CreateDataString(int signal,const char * ptr,int nbytes) const64 std::string BnetDumpPrivate::CreateDataString(int signal,
65                                               const char* ptr,
66                                               int nbytes) const
67 {
68   std::size_t string_length = nbytes - BareosSocketTCP::header_length;
69   string_length = std::min(string_length, max_data_dump_bytes_);
70 
71   std::string data_string(&ptr[BareosSocketTCP::header_length], string_length);
72 
73   if (signal < 0) {
74     data_string
75         = BnetSignalToString(signal) + " - " + BnetSignalToDescription(signal);
76   }
77   std::replace(data_string.begin(), data_string.end(), '\n', ' ');
78   std::replace(data_string.begin(), data_string.end(), '\t', ' ');
79   data_string.erase(
80       std::remove_if(data_string.begin(), data_string.end(),
81                      [](char c) { return !isprint(c) || c == '\r'; }),
82       data_string.end());
83 
84   return data_string;
85 }
86 
CreateFormatStringForNetworkMessage(int signal) const87 std::string BnetDumpPrivate::CreateFormatStringForNetworkMessage(
88     int signal) const
89 {
90   std::string s;
91   if (plantuml_mode_) {
92     if (signal > 998) {  // signal set to 999
93       s = "\"%s\" -> \"%s\": (>%3d) %s\\n";
94     } else if (signal < 0) {  // bnet signal
95       s = "\"%s\" -> \"%s\": (%4d) %s\\n";
96     } else {
97       s = "\"%s\" -> \"%s\": (%4d) %s\\n";
98     }
99   } else {
100     if (signal > 998) {  // signal set to 999
101       s = "%12s -> %-12s: (>%3d) %s\n";
102     } else if (signal < 0) {  // bnet signal
103       s = "%12s -> %-12s: (%4d) %s\n";
104     } else {
105       s = "%12s -> %-12s: (%4d) %s\n";
106     }
107   }
108   return s;
109 }
110 
IsExcludedRcode(const BStringList & l) const111 bool BnetDumpPrivate::IsExcludedRcode(const BStringList& l) const
112 {
113   if (l.size() > 0) {
114     const std::string& probe = l[0];
115     if (exclude_rcodes_.find(probe) != exclude_rcodes_.end()) { return true; }
116   }
117   return false;
118 }
119 
SuppressMessageIfRcodeIsInExcludeList() const120 bool BnetDumpPrivate::SuppressMessageIfRcodeIsInExcludeList() const
121 {
122   BStringList own_name(own_qualified_name_, "::");
123   BStringList destination_name(destination_qualified_name_, "::");
124 
125   return IsExcludedRcode(own_name) || IsExcludedRcode(destination_name);
126 }
127 
CreateAndWriteMessageToBuffer(const char * ptr,int nbytes)128 void BnetDumpPrivate::CreateAndWriteMessageToBuffer(const char* ptr, int nbytes)
129 {
130   static_assert(BareosSocketTCP::header_length == sizeof(int32_t),
131                 "BareosSocket header size does not match");
132   int signal = ntohl(*((int32_t*)&ptr[0]));
133   if (signal > 999) { signal = 999; }
134 
135   std::vector<char> buffer(1024);
136 
137   snprintf(buffer.data(), buffer.size(),
138            CreateFormatStringForNetworkMessage(signal).c_str(),
139            own_qualified_name_.c_str(), destination_qualified_name_.c_str(),
140            signal, CreateDataString(signal, ptr, nbytes).c_str());
141   output_buffer_ = buffer.data();
142 }
143 
CreateAndWriteStacktraceToBuffer()144 void BnetDumpPrivate::CreateAndWriteStacktraceToBuffer()
145 {
146   std::vector<BacktraceInfo> trace_lines(
147       Backtrace(stack_level_start_, stack_level_amount_));
148 
149   std::vector<char> buffer(1024);
150   const char* fmt = plantuml_mode_ ? "(T%3d) %s\\n" : "(T%3d) %s\n";
151 
152   for (const BacktraceInfo& bt : trace_lines) {
153     std::string s(bt.function_call_.c_str(),
154                   std::min(bt.function_call_.size(), max_data_dump_bytes_));
155     snprintf(buffer.data(), buffer.size(), fmt, bt.frame_number_, s.c_str());
156     output_buffer_ += buffer.data();
157   }
158 
159   if (plantuml_mode_) { output_buffer_ += "\n"; }
160 }
161 
DumpToFile(const char * ptr,int nbytes)162 void BnetDumpPrivate::DumpToFile(const char* ptr, int nbytes)
163 {
164   if (SuppressMessageIfRcodeIsInExcludeList()) { return; }
165 
166   if (state_ == State::kRunNormal) {
167     CreateAndWriteMessageToBuffer(ptr, nbytes);
168     CreateAndWriteStacktraceToBuffer();
169     output_file_ << output_buffer_;
170     output_file_.flush();
171 
172   } else if (state_ == State::kWaitForDestinationName) {
173     return;
174   }
175 }
176 
SaveAndSendMessageIfNoDestinationDefined(const char * ptr,int nbytes)177 void BnetDumpPrivate::SaveAndSendMessageIfNoDestinationDefined(const char* ptr,
178                                                                int nbytes)
179 {
180   if (state_ != State::kWaitForDestinationName) { return; }
181 
182   if (destination_qualified_name_.empty()) {
183     std::size_t amount = nbytes;
184     amount = std::min(amount, max_data_dump_bytes_);
185 
186     std::vector<char> temp_data;
187     std::copy(ptr, ptr + amount, std::back_inserter(temp_data));
188 
189     temporary_buffer_for_initial_messages_.push_back(temp_data);
190 
191     if (temporary_buffer_for_initial_messages_.size() > 3) {
192       Dmsg0(100, "BnetDumpPrivate: destination_qualified_name_ not set\n");
193     }
194 
195   } else {  // !empty() -> send all buffered messages
196     state_ = State::kRunNormal;
197     for (auto& v : temporary_buffer_for_initial_messages_) {
198       DumpToFile(v.data(), v.size());
199     }
200     temporary_buffer_for_initial_messages_.clear();
201   }  // destination_qualified_name_.empty()
202 }
203