1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include "traffic_crashlog.h"
25 #include "tscore/ink_args.h"
26 #include "tscore/ink_cap.h"
27 #include "tscore/I_Version.h"
28 #include "tscore/I_Layout.h"
29 #include "tscore/ink_syslog.h"
30 #include "records/I_RecProcess.h"
31 #include "RecordsConfig.h"
32 #include "tscore/BaseLogFile.h"
33 #include "tscore/runroot.h"
34 
35 static int syslog_mode    = false;
36 static int debug_mode     = false;
37 static int wait_mode      = false;
38 static char *host_triplet = nullptr;
39 static int target_pid     = getppid();
40 static char *user         = nullptr;
41 
42 // If pid_t is not sizeof(int), we will have to jiggle argument parsing.
43 extern char __pid_size_static_assert[sizeof(pid_t) == sizeof(int) ? 0 : -1];
44 
45 static AppVersionInfo appVersionInfo;
46 static const ArgumentDescription argument_descriptions[] = {
47   {"target", '-', "Target process ID", "I", &target_pid, nullptr, nullptr},
48   {"host", '-', "Host triplet for the process being logged", "S*", &host_triplet, nullptr, nullptr},
49   {"wait", '-', "Stop until signalled at startup", "F", &wait_mode, nullptr, nullptr},
50   {"syslog", '-', "Syslog after writing a crash log", "F", &syslog_mode, nullptr, nullptr},
51   {"debug", '-', "Enable debugging mode", "F", &debug_mode, nullptr, nullptr},
52   {"user", '-', "Username used to set privileges", "S*", &user, nullptr, nullptr},
53   HELP_ARGUMENT_DESCRIPTION(),
54   VERSION_ARGUMENT_DESCRIPTION(),
55   RUNROOT_ARGUMENT_DESCRIPTION()};
56 
57 static struct tm
timestamp()58 timestamp()
59 {
60   time_t now = time(nullptr);
61   struct tm tm;
62 
63   localtime_r(&now, &tm);
64   return tm;
65 }
66 
67 static char *
crashlog_name()68 crashlog_name()
69 {
70   char filename[64];
71   struct tm now = timestamp();
72   std::string logdir(RecConfigReadLogDir());
73   ats_scoped_str pathname;
74 
75   strftime(filename, sizeof(filename), "crash-%Y-%m-%d-%H%M%S.log", &now);
76   pathname = Layout::relative_to(logdir, filename);
77 
78   return pathname.release();
79 }
80 
81 static FILE *
crashlog_open(const char * path)82 crashlog_open(const char *path)
83 {
84   int fd;
85 
86   fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0400);
87   return (fd == -1) ? nullptr : fdopen(fd, "w");
88 }
89 
90 static unsigned
max_passwd_size()91 max_passwd_size()
92 {
93 #if defined(_SC_GETPW_R_SIZE_MAX)
94   long val = sysconf(_SC_GETPW_R_SIZE_MAX);
95   if (val > 0) {
96     return static_cast<unsigned>(val);
97   }
98 #endif
99 
100   return 4096;
101 }
102 
103 static void
change_privileges()104 change_privileges()
105 {
106   struct passwd *pwd;
107   struct passwd pbuf;
108   char buf[max_passwd_size()];
109 
110   if (getpwnam_r(user, &pbuf, buf, sizeof(buf), &pwd) != 0) {
111     Error("missing password database entry for username '%s': %s", user, strerror(errno));
112     return;
113   }
114 
115   if (pwd == nullptr) {
116     // Password entry not found ...
117     Error("missing password database entry for '%s'", user);
118     return;
119   }
120 
121   if (setegid(pwd->pw_gid) != 0) {
122     Error("setegid(%d) failed: %s", pwd->pw_gid, strerror(errno));
123     return;
124   }
125 
126   if (setreuid(pwd->pw_uid, 0) != 0) {
127     Error("setreuid(%d, %d) failed: %s", pwd->pw_uid, 0, strerror(errno));
128     return;
129   }
130 }
131 
132 int
main(int,const char ** argv)133 main(int /* argc ATS_UNUSED */, const char **argv)
134 {
135   FILE *fp;
136   char *logname;
137   TSMgmtError mgmterr;
138   crashlog_target target;
139   pid_t parent = getppid();
140 
141   diags = new Diags("traffic_crashlog", "" /* tags */, "" /* actions */, new BaseLogFile("stderr"));
142 
143   appVersionInfo.setup(PACKAGE_NAME, "traffic_crashlog", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
144 
145   // Process command line arguments and dump into variables
146   process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
147 
148   // XXX This is a hack. traffic_manager starts traffic_server with the euid of the admin user. We are still
149   // privileged, but won't be able to open files in /proc or ptrace the target. This really should be fixed
150   // in traffic_manager.
151   if (getuid() == 0) {
152     change_privileges();
153   }
154 
155   if (wait_mode) {
156     EnableDeathSignal(SIGKILL);
157     kill(getpid(), SIGSTOP);
158   }
159 
160   // If our parent changed, then we were woken after traffic_server exited. There's no point trying to
161   // emit a crashlog because traffic_server is gone.
162   if (getppid() != parent) {
163     return 0;
164   }
165 
166   runroot_handler(argv);
167   Layout::create();
168   RecProcessInit(RECM_STAND_ALONE, nullptr /* diags */);
169   LibRecordsConfigInit();
170 
171   if (syslog_mode) {
172     RecString name;
173     int facility = -1;
174 
175     if (REC_ReadConfigStringAlloc(name, "proxy.config.syslog_facility") == REC_ERR_OKAY) {
176       facility = facility_string_to_int(name);
177       ats_free(name);
178     }
179 
180     if (facility < 0) {
181       facility = LOG_DAEMON;
182     }
183 
184     openlog(appVersionInfo.AppStr, LOG_PID | LOG_NDELAY | LOG_NOWAIT, facility);
185     diags->config.outputs[DL_Debug].to_syslog     = true;
186     diags->config.outputs[DL_Status].to_syslog    = true;
187     diags->config.outputs[DL_Note].to_syslog      = true;
188     diags->config.outputs[DL_Warning].to_syslog   = true;
189     diags->config.outputs[DL_Error].to_syslog     = true;
190     diags->config.outputs[DL_Fatal].to_syslog     = true;
191     diags->config.outputs[DL_Alert].to_syslog     = true;
192     diags->config.outputs[DL_Emergency].to_syslog = true;
193   }
194 
195   Note("crashlog started, target=%ld, debug=%s syslog=%s, uid=%ld euid=%ld", (long)target_pid, debug_mode ? "true" : "false",
196        syslog_mode ? "true" : "false", (long)getuid(), (long)geteuid());
197 
198   mgmterr = TSInit(nullptr, (TSInitOptionT)(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
199   if (mgmterr != TS_ERR_OKAY) {
200     char *msg = TSGetErrorMessage(mgmterr);
201     Warning("failed to initialize management API: %s", msg);
202     TSfree(msg);
203   }
204 
205   ink_zero(target);
206   target.pid       = static_cast<pid_t>(target_pid);
207   target.timestamp = timestamp();
208 
209   if (host_triplet && strncmp(host_triplet, "x86_64-unknown-linux", sizeof("x86_64-unknown-linux") - 1) == 0) {
210     ssize_t nbytes;
211     target.flags |= CRASHLOG_HAVE_THREADINFO;
212 
213     nbytes = read(STDIN_FILENO, &target.siginfo, sizeof(target.siginfo));
214     if (nbytes < static_cast<ssize_t>(sizeof(target.siginfo))) {
215       Warning("received %zd of %zu expected signal info bytes", nbytes, sizeof(target.siginfo));
216       target.flags &= ~CRASHLOG_HAVE_THREADINFO;
217     }
218 
219     nbytes = read(STDIN_FILENO, &target.ucontext, sizeof(target.ucontext));
220     if (nbytes < static_cast<ssize_t>(sizeof(target.ucontext))) {
221       Warning("received %zd of %zu expected thread context bytes", nbytes, sizeof(target.ucontext));
222       target.flags &= ~CRASHLOG_HAVE_THREADINFO;
223     }
224   }
225 
226   logname = crashlog_name();
227 
228   fp = debug_mode ? stdout : crashlog_open(logname);
229   if (fp == nullptr) {
230     Error("failed to create '%s': %s", logname, strerror(errno));
231     ats_free(logname);
232     return 1;
233   }
234 
235   Note("logging to %p", fp);
236 
237   crashlog_write_procname(fp, target);
238   crashlog_write_exename(fp, target);
239   fprintf(fp, LABELFMT "Traffic Server %s\n", "Version:", PACKAGE_VERSION);
240   crashlog_write_uname(fp, target);
241   crashlog_write_datime(fp, target);
242 
243   fprintf(fp, "\n");
244   crashlog_write_siginfo(fp, target);
245 
246   fprintf(fp, "\n");
247   crashlog_write_registers(fp, target);
248 
249   fprintf(fp, "\n");
250   crashlog_write_backtrace(fp, target);
251 
252   fprintf(fp, "\n");
253   crashlog_write_procstatus(fp, target);
254 
255   fprintf(fp, "\n");
256   crashlog_write_proclimits(fp, target);
257 
258   fprintf(fp, "\n");
259   crashlog_write_regions(fp, target);
260 
261   fprintf(fp, "\n");
262   crashlog_write_records(fp, target);
263 
264   Error("wrote crash log to %s", logname);
265 
266   ats_free(logname);
267 
268   fflush(fp);
269   fclose(fp);
270   return 0;
271 }
272