1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program 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. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf3.defs.h>
26 #include <cf-execd-runner.h>
27
28 #include <files_names.h>
29 #include <files_interfaces.h>
30 #include <string_lib.h>
31 #include <pipes.h>
32 #include <unix.h>
33 #include <mutex.h>
34 #include <global_mutex.h>
35 #include <signals.h>
36 #include <exec_tools.h>
37 #include <misc_lib.h>
38 #include <file_lib.h>
39 #include <assert.h>
40 #include <crypto.h>
41 #include <known_dirs.h>
42 #include <bootstrap.h>
43 #include <policy_server.h>
44 #include <hash.h>
45 #include <item_lib.h>
46 #include <regex.h> /* StringMatchFullWithPrecompiledRegex() */
47 #include <processes_select.h> /* LoadProcessTable()/SelectProcesses() */
48
49 #include <cf-windows-functions.h>
50
51 /*******************************************************************/
52
53 static const int INF_LINES = -2;
54
55 /*******************************************************************/
56
57 static void MailResult(const ExecConfig *config, const char *file);
58 static bool Dialogue(int sd, const char *s);
59
60 /******************************************************************************/
61
62 # if defined(__MINGW32__)
63
ThreadUniqueName(void)64 static void *ThreadUniqueName(void)
65 {
66 return pthread_self().p;
67 }
68
69 # else /* __MINGW32__ */
70
ThreadUniqueName(void)71 static void *ThreadUniqueName(void)
72 {
73 return (void *)pthread_self();
74 }
75
76 # endif /* __MINGW32__ */
77
TwinFilename(void)78 static const char *TwinFilename(void)
79 {
80 #if defined(_WIN32)
81 return "bin-twin/cf-agent.exe";
82 #else
83 return "bin/cf-twin";
84 #endif
85 }
86
AgentFilename(void)87 static const char *AgentFilename(void)
88 {
89 #if defined(_WIN32)
90 return "bin/cf-agent.exe";
91 #else
92 return "bin/cf-agent";
93 #endif
94 }
95
TwinExists(void)96 static bool TwinExists(void)
97 {
98 char twinfilename[CF_BUFSIZE];
99 struct stat sb;
100
101 snprintf(twinfilename, CF_BUFSIZE, "%s/%s", GetWorkDir(), TwinFilename());
102
103 MapName(twinfilename);
104
105 return (stat(twinfilename, &sb) == 0) && (IsExecutable(twinfilename));
106 }
107
108 /* Buffer has to be at least CF_BUFSIZE bytes long */
ConstructFailsafeCommand(bool scheduled_run,char * buffer)109 static void ConstructFailsafeCommand(bool scheduled_run, char *buffer)
110 {
111 bool twin_exists = TwinExists();
112
113 const char* const workdir = GetWorkDir();
114
115 snprintf(buffer, CF_BUFSIZE,
116 "\"%s%c%s\" -f failsafe.cf "
117 "&& \"%s%c%s\" -Dfrom_cfexecd%s",
118 workdir, FILE_SEPARATOR, twin_exists ? TwinFilename() : AgentFilename(),
119 workdir, FILE_SEPARATOR, AgentFilename(), scheduled_run ? ",scheduled_run" : "");
120 }
121
122 #ifndef __MINGW32__
123
124 #if defined(__hpux) && defined(__GNUC__)
125 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
126 // Avoid spurious HP-UX GCC type-pun warning on FD_SET() macro
127 #endif
128
IsReadReady(int fd,int timeout_sec)129 static bool IsReadReady(int fd, int timeout_sec)
130 {
131 fd_set rset;
132 FD_ZERO(&rset);
133 FD_SET(fd, &rset);
134
135 struct timeval tv = {
136 .tv_sec = timeout_sec,
137 .tv_usec = 0,
138 };
139
140 int ret = select(fd + 1, &rset, NULL, NULL, &tv);
141
142 if(ret < 0)
143 {
144 Log(LOG_LEVEL_ERR, "IsReadReady: Failed checking for data. (select: %s)", GetErrorStr());
145 return false;
146 }
147
148 if(FD_ISSET(fd, &rset))
149 {
150 return true;
151 }
152
153 if(ret == 0) // timeout
154 {
155 return false;
156 }
157
158 // can we get here?
159 Log(LOG_LEVEL_ERR, "IsReadReady: Unknown outcome (ret > 0 but our only fd is not set). (select: %s)", GetErrorStr());
160
161 return false;
162 }
163 #if defined(__hpux) && defined(__GNUC__)
164 #pragma GCC diagnostic warning "-Wstrict-aliasing"
165 #endif
166
167 #endif /* __MINGW32__ */
168
LocalExec(const ExecConfig * config)169 void LocalExec(const ExecConfig *config)
170 {
171 time_t starttime = time(NULL);
172
173 void *thread_name = ThreadUniqueName();
174
175 {
176 char starttime_str[64];
177 cf_strtimestamp_local(starttime, starttime_str);
178
179 Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------");
180 Log(LOG_LEVEL_VERBOSE, " LocalExec(%sscheduled) at %s", config->scheduled_run ? "" : "not ", starttime_str);
181 Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------");
182 }
183
184 /* Need to make sure we have LD_LIBRARY_PATH here or children will die */
185
186 char cmd[CF_BUFSIZE];
187 if (strlen(config->exec_command) > 0)
188 {
189 strlcpy(cmd, config->exec_command, CF_BUFSIZE);
190 }
191 else
192 {
193 ConstructFailsafeCommand(config->scheduled_run, cmd);
194 }
195
196 char esc_command[CF_BUFSIZE];
197 strlcpy(esc_command, MapName(cmd), CF_BUFSIZE);
198
199
200 char filename[CF_BUFSIZE];
201 {
202 // 2 underscores, longest 64 bit integer, -1 for NUL byte, 26 for ctime (including NUL)
203 char line[2 + sizeof("-9223372036854775808") - 1 + 26];
204 snprintf(line, sizeof(line), "_%jd_%s", (intmax_t) starttime, CanonifyName(ctime(&starttime)));
205 {
206 char canonified_fq_name[sizeof(VFQNAME)];
207
208 strlcpy(canonified_fq_name, config->fq_name, sizeof(canonified_fq_name));
209 CanonifyNameInPlace(canonified_fq_name);
210
211 snprintf(filename, CF_BUFSIZE, "%s/outputs/cf_%s_%s_%p",
212 GetWorkDir(), canonified_fq_name, line, thread_name);
213
214 MapName(filename);
215 }
216 }
217
218
219 /* What if no more processes? Could sacrifice and exec() - but we need a sentinel */
220
221 FILE *fp = safe_fopen(filename, "w");
222 if (fp == NULL)
223 {
224 Log(LOG_LEVEL_ERR, "Couldn't open '%s' - aborting exec. (fopen: %s)", filename, GetErrorStr());
225 return;
226 }
227
228 /*
229 * Don't inherit this file descriptor on fork/exec
230 */
231
232 if (fileno(fp) != -1)
233 {
234 SetCloseOnExec(fileno(fp), true);
235 }
236
237 Log(LOG_LEVEL_VERBOSE, "Command => %s", cmd);
238
239 FILE *pp = cf_popen_sh(esc_command, "r");
240 if (!pp)
241 {
242 Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", cmd, GetErrorStr());
243 fclose(fp);
244 return;
245 }
246
247 Log(LOG_LEVEL_VERBOSE, "Command is executing...%s", esc_command);
248
249 int count = 0;
250 int complete = false;
251 size_t line_size = CF_BUFSIZE;
252 char *line = xmalloc(line_size);
253
254 while (!IsPendingTermination())
255 {
256 if (!IsReadReady(fileno(pp),
257 config->agent_expireafter * SECONDS_PER_MINUTE))
258 {
259 char errmsg[] =
260 "cf-execd: timeout waiting for output from agent"
261 " (agent_expireafter=%d) - terminating it\n";
262
263 fprintf(fp, errmsg, config->agent_expireafter);
264 /* Trim '\n' before Log()ing. */
265 errmsg[strlen(errmsg) - 1] = '\0';
266 Log(LOG_LEVEL_NOTICE, errmsg, config->agent_expireafter);
267 count++;
268
269 pid_t pid_shell;
270
271 if (PipeToPid(&pid_shell, pp))
272 {
273 /* Default to killing the shell process (if we fail to get
274 * more precise target). */
275 pid_t pid_to_kill = pid_shell;
276
277 #ifndef __MINGW32__
278 /* The agent command is executed in a shell. Trying to kill the
279 * shell may end up sending it SIGKILL which is not propagated
280 * to the subprocesses of the shell and thus the cf-agent
281 * process. The shell, however, creates a new process group
282 * (with the PGID equal to the PID of the child process) for the
283 * agent which then allows us to kill the whole process group
284 * here. */
285
286 /* We need to determine the PID of the agent (and thus its
287 * process group) first.*/
288 ClearProcessTable();
289 if (LoadProcessTable())
290 {
291 ProcessSelect ps = PROCESS_SELECT_INIT;
292 ps.min_ppid = pid_shell;
293 ps.max_ppid = pid_shell;
294 Item *procs = SelectProcesses(".*" /* any command */, &ps, true /* apply ps */);
295 if (procs != NULL)
296 {
297 pid_to_kill = procs->counter;
298
299 /* There should only be one child process of the shell
300 * running by default. But it doesn't apply to all
301 * values of exec_command in general. */
302 assert(procs->next == NULL);
303 }
304 }
305
306 /* kill(-pid) is actually kill(pgid=pid) and kill(pid, 0) just
307 * checks if it's possible to send signals to pid (or pgid in
308 * our case). Kill the whole process group if possible. */
309 if ((getpgid(pid_to_kill) == pid_to_kill)
310 && (kill(-pid_to_kill, 0) == 0))
311 {
312 pid_to_kill = -pid_to_kill;
313 }
314 #endif
315 ProcessSignalTerminate(pid_to_kill);
316 }
317 else
318 {
319 Log(LOG_LEVEL_ERR, "Could not get PID of agent");
320 }
321
322 break;
323 }
324
325 ssize_t res = CfReadLine(&line, &line_size, pp);
326 if (res == -1)
327 {
328 if (feof(pp))
329 {
330 complete = true;
331 }
332 else
333 {
334 Log(LOG_LEVEL_ERR,
335 "Unable to read output from command '%s'. (cfread: %s)",
336 cmd, GetErrorStr());
337 }
338 break;
339 }
340
341 const char *sp = line;
342 while (*sp != '\0' && isspace(*sp))
343 {
344 sp++;
345 }
346
347 if (*sp != '\0') /* line isn't entirely blank */
348 {
349 /* Need space for 2x buffer and null byte. */
350 char line_escaped[res * 2 + 1];
351 memcpy(line_escaped, line, res + 1);
352
353 ssize_t replace_res =
354 StringReplace(line_escaped, sizeof(line_escaped), "%", "%%");
355 if (replace_res == -1)
356 {
357 ProgrammingError("StringReplace(): buffer overflow in %s",
358 __FILE__);
359 line[0] = '\0';
360 continue;
361 }
362
363 fprintf(fp, "%s\n", line_escaped);
364 count++;
365
366 /* If we can't send mail, log to syslog */
367
368 if (strlen(config->mail_to_address) == 0)
369 {
370 strncat(line_escaped, "\n", sizeof(line_escaped) - 1 - strlen(line_escaped));
371 if ((strchr(line_escaped, '\n')) == NULL)
372 {
373 line_escaped[sizeof(line_escaped) - 2] = '\n';
374 }
375
376 Log(LOG_LEVEL_INFO, "%s", line_escaped);
377 }
378
379 line[0] = '\0';
380 }
381 }
382
383 free(line);
384 cf_pclose(pp);
385 Log(LOG_LEVEL_VERBOSE,
386 complete ? "Command is complete" : "Terminated command");
387
388 if (count)
389 {
390 Log(LOG_LEVEL_DEBUG, "Closing fp");
391 fclose(fp);
392 Log(LOG_LEVEL_VERBOSE, "Mailing result");
393 MailResult(config, filename);
394 }
395 else
396 {
397 Log(LOG_LEVEL_VERBOSE, "No output");
398 unlink(filename);
399 fclose(fp);
400 }
401 }
402
403 // Returns true if line is filtered, IOW should not be included.
LineIsFiltered(const ExecConfig * config,const char * line)404 static bool LineIsFiltered(const ExecConfig *config,
405 const char *line)
406 {
407 // Check whether the line matches mail filters
408 int include_filter_len = SeqLength(config->mailfilter_include_regex);
409 int exclude_filter_len = SeqLength(config->mailfilter_exclude_regex);
410 // Count messages as matched in include set if there is no include set.
411 bool included = (include_filter_len == 0);
412 bool excluded = false;
413 if (include_filter_len > 0)
414 {
415 for (int i = 0; i < include_filter_len; i++)
416 {
417 if (StringMatchFullWithPrecompiledRegex(SeqAt(config->mailfilter_include_regex, i),
418 line))
419 {
420 included = true;
421 }
422 }
423 }
424 if (exclude_filter_len > 0)
425 {
426 for (int i = 0; i < exclude_filter_len; i++)
427 {
428 if (StringMatchFullWithPrecompiledRegex(SeqAt(config->mailfilter_exclude_regex, i),
429 line))
430 {
431 excluded = true;
432 }
433 }
434 }
435 return !included || excluded;
436 }
437
CompareResultEqualOrFiltered(const ExecConfig * config,const char * filename,const char * prev_file)438 static bool CompareResultEqualOrFiltered(const ExecConfig *config,
439 const char *filename,
440 const char *prev_file)
441 {
442 Log(LOG_LEVEL_VERBOSE, "Comparing files %s with %s", prev_file, filename);
443
444 bool rtn = true;
445
446 FILE *old_fp = safe_fopen(prev_file, "r");
447 FILE *new_fp = safe_fopen(filename, "r");
448 if (new_fp)
449 {
450 const char *errptr;
451 int erroffset;
452 pcre_extra *regex_extra = NULL;
453 // Match timestamps and remove them. Not Y21K safe! :-)
454 pcre *regex = pcre_compile(LOGGING_TIMESTAMP_REGEX, PCRE_MULTILINE, &errptr, &erroffset, NULL);
455 if (!regex)
456 {
457 UnexpectedError("Compiling regular expression failed");
458 rtn = false;
459 }
460 else
461 {
462 regex_extra = pcre_study(regex, 0, &errptr);
463 }
464
465 size_t old_line_size = CF_BUFSIZE;
466 char *old_line = xmalloc(old_line_size);
467 size_t new_line_size = CF_BUFSIZE;
468 char *new_line = xmalloc(new_line_size);
469 bool any_new_msg_present = false;
470 while (regex)
471 {
472 char *old_msg = NULL;
473 if (old_fp)
474 {
475 while (CfReadLine(&old_line, &old_line_size, old_fp) >= 0)
476 {
477 if (!LineIsFiltered(config, old_line))
478 {
479 old_msg = old_line;
480 break;
481 }
482 }
483 }
484
485 char *new_msg = NULL;
486 while (CfReadLine(&new_line, &new_line_size, new_fp) >= 0)
487 {
488 if (!LineIsFiltered(config, new_line))
489 {
490 any_new_msg_present = true;
491 new_msg = new_line;
492 break;
493 }
494 }
495
496 if (!old_msg || !new_msg)
497 {
498 // Return difference in most cases, when there is a new
499 // message line, but if there isn't one, return equal, even
500 // if strictly speaking they aren't, since we don't want to
501 // send an empty email.
502 if (any_new_msg_present && old_msg != new_msg)
503 {
504 rtn = false;
505 }
506 break;
507 }
508
509 // Remove timestamps from lines before comparison.
510 char *index;
511 if (pcre_exec(regex, regex_extra, old_msg, strlen(old_msg), 0, 0, NULL, 0) >= 0)
512 {
513 index = strstr(old_msg, ": ");
514 if (index != NULL)
515 {
516 old_msg = index + 2;
517 }
518 }
519 if (pcre_exec(regex, regex_extra, new_msg, strlen(new_msg), 0, 0, NULL, 0) >= 0)
520 {
521 index = strstr(new_msg, ": ");
522 if (index != NULL)
523 {
524 new_msg = index + 2;
525 }
526 }
527
528 if (strcmp(old_msg, new_msg) != 0)
529 {
530 rtn = false;
531 break;
532 }
533 }
534
535 free(old_line);
536 free(new_line);
537
538 if (regex_extra)
539 {
540 free(regex_extra);
541 }
542 pcre_free(regex);
543 }
544 else
545 {
546 /* no previous file */
547 rtn = false;
548 }
549
550 if (old_fp)
551 {
552 fclose(old_fp);
553 }
554 if (new_fp)
555 {
556 fclose(new_fp);
557 }
558
559 ThreadLock(cft_count);
560
561 /* replace old file with new*/
562
563 unlink(prev_file);
564
565 if (!LinkOrCopy(filename, prev_file, true))
566 {
567 Log(LOG_LEVEL_INFO, "Could not symlink or copy '%s' to '%s'", filename, prev_file);
568 rtn = false;
569 }
570
571 ThreadUnlock(cft_count);
572 return rtn;
573 }
574
575 #ifndef TEST_CF_EXECD
ConnectToSmtpSocket(const ExecConfig * config)576 int ConnectToSmtpSocket(const ExecConfig *config)
577 {
578 struct hostent *hp = gethostbyname(config->mail_server);
579 if (!hp)
580 {
581 Log(LOG_LEVEL_ERR, "Mail report: unknown host '%s' ('smtpserver' in body executor control). Make sure that fully qualified names can be looked up at your site.",
582 config->mail_server);
583 return -1;
584 }
585
586 struct servent *server = getservbyname("smtp", "tcp");
587 if (!server)
588 {
589 Log(LOG_LEVEL_ERR, "Mail report: unable to lookup smtp service. (getservbyname: %s)", GetErrorStr());
590 return -1;
591 }
592
593 struct sockaddr_in raddr;
594 memset(&raddr, 0, sizeof(raddr));
595
596 raddr.sin_port = (unsigned int) server->s_port;
597 raddr.sin_addr.s_addr = ((struct in_addr *) (hp->h_addr))->s_addr;
598 raddr.sin_family = AF_INET;
599
600 Log(LOG_LEVEL_DEBUG, "Mail report: connecting...");
601
602 int sd = socket(AF_INET, SOCK_STREAM, 0);
603 if (sd == -1)
604 {
605 Log(LOG_LEVEL_ERR, "Mail report: couldn't open a socket. (socket: %s)", GetErrorStr());
606 return -1;
607 }
608
609 if (connect(sd, (void *) &raddr, sizeof(raddr)) == -1)
610 {
611 Log(LOG_LEVEL_ERR, "Mail report: couldn't connect to host '%s'. (connect: %s)",
612 config->mail_server, GetErrorStr());
613 cf_closesocket(sd);
614 return -1;
615 }
616
617 return sd;
618 }
619 #endif // !TEST_CF_EXECD
620
MailResult(const ExecConfig * config,const char * file)621 static void MailResult(const ExecConfig *config, const char *file)
622 {
623 /* Must be initialised to NULL, so that free(line) works
624 when goto mail_err. */
625 char *line = NULL;
626
627 #if defined __linux__ || defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__
628 time_t now = time(NULL);
629 #endif
630
631 FILE *fp = safe_fopen(file, "r");
632 if (fp == NULL)
633 {
634 Log(LOG_LEVEL_ERR, "Mail report: couldn't open file '%s'. (fopen: %s)", file, GetErrorStr());
635 return;
636 }
637
638 Log(LOG_LEVEL_VERBOSE, "Mail report: sending result...");
639
640 {
641 int fd = fileno(fp);
642 struct stat statbuf;
643 if (fstat(fd, &statbuf) == -1)
644 {
645 Log(LOG_LEVEL_ERR, "Mail report: failed to stat file '%s' [errno: %d]", file, errno);
646 fclose(fp);
647 return;
648 }
649
650 if (statbuf.st_size == 0)
651 {
652 unlink(file);
653 fclose(fp);
654 Log(LOG_LEVEL_DEBUG, "Mail report: nothing to report in file '%s'", file);
655 return;
656 }
657 }
658
659 {
660 char prev_file[CF_BUFSIZE];
661 snprintf(prev_file, CF_BUFSIZE, "%s/outputs/previous", GetWorkDir());
662 MapName(prev_file);
663
664 if (CompareResultEqualOrFiltered(config, file, prev_file))
665 {
666 Log(LOG_LEVEL_VERBOSE, "Mail report: previous output is the same as current so do not mail it");
667 fclose(fp);
668 return;
669 }
670 }
671
672 if ((strlen(config->mail_server) == 0) || (strlen(config->mail_to_address) == 0))
673 {
674 /* Syslog should have done this */
675 Log(LOG_LEVEL_VERBOSE, "Mail report: empty mail server or address - skipping");
676 fclose(fp);
677 return;
678 }
679
680 if (config->mail_max_lines == 0)
681 {
682 Log(LOG_LEVEL_DEBUG, "Mail report: not mailing because EmailMaxLines was zero");
683 fclose(fp);
684 return;
685 }
686
687 Log(LOG_LEVEL_DEBUG, "Mail report: mailing results of '%s' to '%s'", file, config->mail_to_address);
688
689 int sd = ConnectToSmtpSocket(config);
690 if (sd < 0)
691 {
692 fclose(fp);
693 return;
694 }
695
696 /* read greeting */
697
698 if (!Dialogue(sd, NULL))
699 {
700 goto mail_err;
701 }
702
703 char vbuff[CF_BUFSIZE];
704 snprintf(vbuff, sizeof(vbuff), "HELO %s\r\n", config->fq_name);
705 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
706
707 if (!Dialogue(sd, vbuff))
708 {
709 goto mail_err;
710 }
711
712 if (strlen(config->mail_from_address) == 0)
713 {
714 snprintf(vbuff, sizeof(vbuff), "MAIL FROM: <cfengine@%s>\r\n",
715 config->fq_name);
716 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
717 }
718 else
719 {
720 snprintf(vbuff, sizeof(vbuff), "MAIL FROM: <%s>\r\n",
721 config->mail_from_address);
722 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
723 }
724
725 if (!Dialogue(sd, vbuff))
726 {
727 goto mail_err;
728 }
729
730 snprintf(vbuff, sizeof(vbuff), "RCPT TO: <%s>\r\n",
731 config->mail_to_address);
732 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
733
734 if (!Dialogue(sd, vbuff))
735 {
736 goto mail_err;
737 }
738
739 if (!Dialogue(sd, "DATA\r\n"))
740 {
741 goto mail_err;
742 }
743
744 if (SafeStringLength(config->mail_subject) == 0)
745 {
746 snprintf(vbuff, sizeof(vbuff), "Subject: [%s/%s]\r\n", config->fq_name, config->ip_address);
747 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
748 }
749 else
750 {
751 snprintf(vbuff, sizeof(vbuff), "Subject: %s\r\n", config->mail_subject);
752 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
753 }
754
755 send(sd, vbuff, strlen(vbuff), 0);
756
757 /* Send X-CFEngine SMTP header */
758 unsigned char digest[EVP_MAX_MD_SIZE + 1];
759 char buffer[CF_HOSTKEY_STRING_SIZE];
760
761 char *existing_policy_server = PolicyServerReadFile(GetWorkDir());
762 if (!existing_policy_server)
763 {
764 existing_policy_server = xstrdup("(none)");
765 }
766
767 HashPubKey(PUBKEY, digest, CF_DEFAULT_DIGEST);
768
769 snprintf(vbuff, sizeof(vbuff),
770 "X-CFEngine: vfqhost=\"%s\";ip-addresses=\"%s\";policyhub=\"%s\";pkhash=\"%s\"\r\n",
771 VFQNAME, config->ip_addresses, existing_policy_server,
772 HashPrintSafe(buffer, sizeof(buffer), digest, CF_DEFAULT_DIGEST, true));
773
774 send(sd, vbuff, strlen(vbuff), 0);
775 free(existing_policy_server);
776
777 #if defined __linux__ || defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__
778 {
779 struct tm tm_value;
780 struct tm *const tm_pointer = localtime_r(&now, &tm_value);
781 strftime(vbuff, CF_BUFSIZE, "Date: %a, %d %b %Y %H:%M:%S %z\r\n", tm_pointer);
782 }
783 send(sd, vbuff, strlen(vbuff), 0);
784 #endif
785
786 if (strlen(config->mail_from_address) == 0)
787 {
788 snprintf(vbuff, sizeof(vbuff), "From: cfengine@%s\r\n",
789 config->fq_name);
790 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
791 }
792 else
793 {
794 snprintf(vbuff, sizeof(vbuff), "From: %s\r\n",
795 config->mail_from_address);
796 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
797 }
798
799 send(sd, vbuff, strlen(vbuff), 0);
800
801 snprintf(vbuff, sizeof(vbuff), "To: %s\r\n\r\n", config->mail_to_address);
802 Log(LOG_LEVEL_DEBUG, "Mail report: %s", vbuff);
803 send(sd, vbuff, strlen(vbuff), 0);
804
805 size_t line_size = CF_BUFSIZE;
806 line = xmalloc(line_size);
807 ssize_t n_read;
808
809 int count = 0;
810 while ((n_read = CfReadLine(&line, &line_size, fp)) > 0)
811 {
812 if (LineIsFiltered(config, line))
813 {
814 continue;
815 }
816 if (send(sd, line, n_read, 0) == -1 ||
817 send(sd, "\r\n", 2, 0) == -1)
818 {
819 Log(LOG_LEVEL_ERR, "Error while sending mail to mailserver "
820 "'%s'. (send: '%s')", config->mail_server, GetErrorStr());
821 goto mail_err;
822 }
823
824 count++;
825 if ((config->mail_max_lines != INF_LINES) &&
826 (count >= config->mail_max_lines))
827 {
828 snprintf(line, line_size,
829 "\r\n[Mail truncated by CFEngine. File is at %s on %s]\r\n",
830 file, config->fq_name);
831 if (send(sd, line, strlen(line), 0) == -1)
832 {
833 Log(LOG_LEVEL_ERR, "Error while sending mail to mailserver "
834 "'%s'. (send: '%s')", config->mail_server, GetErrorStr());
835 goto mail_err;
836 }
837 break;
838 }
839 }
840
841 if (!Dialogue(sd, ".\r\n"))
842 {
843 Log(LOG_LEVEL_DEBUG, "Mail report: mail_err\n");
844 goto mail_err;
845 }
846
847 Dialogue(sd, "QUIT\r\n");
848 Log(LOG_LEVEL_DEBUG, "Mail report: done sending mail");
849 free(line);
850 fclose(fp);
851 cf_closesocket(sd);
852 return;
853
854 mail_err:
855
856 free(line);
857 fclose(fp);
858 cf_closesocket(sd);
859 Log(LOG_LEVEL_ERR, "Mail report: cannot mail to %s.", config->mail_to_address);
860 }
861
Dialogue(int sd,const char * s)862 static bool Dialogue(int sd, const char *s)
863 {
864 if ((s != NULL) && (*s != '\0'))
865 {
866 int sent = send(sd, s, strlen(s), 0);
867 Log(LOG_LEVEL_DEBUG, "SENT(%d) -> '%s'", sent, s);
868 }
869 else
870 {
871 Log(LOG_LEVEL_DEBUG, "Nothing to send .. waiting for opening");
872 }
873
874 int charpos = 0;
875 int rfclinetype = ' ';
876
877 char ch, f = '\0';
878 while (recv(sd, &ch, 1, 0))
879 {
880 charpos++;
881
882 if (f == '\0')
883 {
884 f = ch;
885 }
886
887 if (charpos == 4) /* Multiline RFC in form 222-Message with hyphen at pos 4 */
888 {
889 rfclinetype = ch;
890 }
891
892 if ((ch == '\n') || (ch == '\0'))
893 {
894 charpos = 0;
895
896 if (rfclinetype == ' ')
897 {
898 break;
899 }
900 }
901 }
902
903 return ((f == '2') || (f == '3')); /* return code 200 or 300 from smtp */
904 }
905