diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 6e8a60e69..89daa9cb6 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -181,6 +181,7 @@ echo "Backing up original files..." # Define files to backup in a shell variable FILES_TO_BACKUP=( + "/usr/local/emhttp/plugins/dynamix/scripts/notify" "/usr/local/emhttp/plugins/dynamix/DisplaySettings.page" "/usr/local/emhttp/plugins/dynamix/Registration.page" "/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php" @@ -325,6 +326,7 @@ exit 0 # Define files to restore in a shell variable - must match backup list FILES_TO_RESTORE=( + "/usr/local/emhttp/plugins/dynamix/scripts/notify" "/usr/local/emhttp/plugins/dynamix/DisplaySettings.page" "/usr/local/emhttp/plugins/dynamix/Registration.page" "/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php" diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/scripts/notify b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/scripts/notify new file mode 100644 index 000000000..ce504b2a2 --- /dev/null +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix/scripts/notify @@ -0,0 +1,332 @@ +#!/usr/bin/php -q + +", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]; + $string = trim(str_replace($special_chars, "", $string)); + $string = preg_replace('~[^0-9a-z _\-]~i', '', $string); + $string = preg_replace('~[- ]~i', '_', $string); + // limit filename length to $maxLength characters + return substr(trim($string), 0, $maxLength); +} + +/* + Call this when using the subject field in email or agents. Do not use when showing the subject in a browser. + Removes all HTML entities from subject line, is specifically targetting the my_temp() function, which adds ' °' +*/ +function clean_subject($subject) { + $subject = preg_replace("/&#?[a-z0-9]{2,8};/i"," ",$subject); + return $subject; +} + +/** + * Wrap string values in double quotes for INI compatibility and escape quotes/backslashes. + * Numeric types remain unquoted so they can be parsed as-is. + */ +function ini_encode_value($value) { + if (is_int($value) || is_float($value)) return $value; + if (is_bool($value)) return $value ? 'true' : 'false'; + $value = (string)$value; + return '"'.strtr($value, ["\\"=>"\\\\", '"' => '\\"']).'"'; +} + +function build_ini_string(array $data) { + $lines = []; + foreach ($data as $key => $value) { + $lines[] = "{$key}=".ini_encode_value($value); + } + return implode("\n", $lines)."\n"; +} + +/** + * Trims and unescapes strings (eg quotes, backslashes) if necessary. + */ +function ini_decode_value($value) { + $value = trim($value); + $length = strlen($value); + if ($length >= 2 && $value[0] === '"' && $value[$length-1] === '"') { + return stripslashes(substr($value, 1, -1)); + } + return $value; +} + +// start +if ($argc == 1) exit(usage()); + +extract(parse_plugin_cfg("dynamix",true)); + +$path = _var($notify,'path','/tmp/notifications'); +$unread = "$path/unread"; +$archive = "$path/archive"; +$agents_dir = "/boot/config/plugins/dynamix/notifications/agents"; +if (is_dir($agents_dir)) { + $agents = []; + foreach (array_diff(scandir($agents_dir), ['.','..']) as $p) { + if (file_exists("{$agents_dir}/{$p}")) $agents[] = "{$agents_dir}/{$p}"; + } +} else { + $agents = NULL; +} + +switch ($argv[1][0]=='-' ? 'add' : $argv[1]) { +case 'init': + $files = glob("$unread/*.notify", GLOB_NOSORT); + foreach ($files as $file) if (!is_readable($file)) chmod($file,0666); + break; + +case 'smtp-init': + @mkdir($unread,0755,true); + @mkdir($archive,0755,true); + $conf = []; + $conf[] = "# Generated settings:"; + $conf[] = "Root={$ssmtp['root']}"; + $domain = strtok($ssmtp['root'],'@'); + $domain = strtok('@'); + $conf[] = "rewriteDomain=$domain"; + $conf[] = "FromLineOverride=YES"; + $conf[] = "Mailhub={$ssmtp['server']}:{$ssmtp['port']}"; + $conf[] = "UseTLS={$ssmtp['UseTLS']}"; + $conf[] = "UseSTARTTLS={$ssmtp['UseSTARTTLS']}"; + if ($ssmtp['AuthMethod'] != "none") { + $conf[] = "AuthMethod={$ssmtp['AuthMethod']}"; + $conf[] = "AuthUser={$ssmtp['AuthUser']}"; + $conf[] = "AuthPass=".base64_decrypt($ssmtp['AuthPass']); + } + $conf[] = ""; + file_put_contents("/etc/ssmtp/ssmtp.conf", implode("\n", $conf)); + break; + +case 'cron-init': + @mkdir($unread,0755,true); + @mkdir($archive,0755,true); + $text = empty($notify['status']) ? "" : "# Generated array status check schedule:\n{$notify['status']} $docroot/plugins/dynamix/scripts/statuscheck &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "status-check", $text); + $text = empty($notify['unraidos']) ? "" : "# Generated Unraid OS update check schedule:\n{$notify['unraidos']} $docroot/plugins/dynamix.plugin.manager/scripts/unraidcheck &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "unraid-check", $text); + $text = empty($notify['version']) ? "" : "# Generated plugins version check schedule:\n{$notify['version']} $docroot/plugins/dynamix.plugin.manager/scripts/plugincheck &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "plugin-check", $text); + $text = empty($notify['system']) ? "" : "# Generated system monitoring schedule:\n{$notify['system']} $docroot/plugins/dynamix/scripts/monitor &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "monitor", $text); + $text = empty($notify['docker_update']) ? "" : "# Generated docker monitoring schedule:\n{$notify['docker_update']} $docroot/plugins/dynamix.docker.manager/scripts/dockerupdate check &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "docker-update", $text); + $text = empty($notify['language_update']) ? "" : "# Generated languages version check schedule:\n{$notify['language_update']} $docroot/plugins/dynamix.plugin.manager/scripts/languagecheck &> /dev/null\n\n"; + parse_cron_cfg("dynamix", "language-check", $text); + break; + +case 'add': + $event = 'Unraid Status'; + $subject = 'Notification'; + $description = 'No description'; + $importance = 'normal'; + $message = $recipients = $link = $fqdnlink = ''; + $timestamp = time(); + $ticket = $timestamp; + $mailtest = false; + $overrule = false; + $noBrowser = false; + $customFilename = false; + + $options = getopt("l:e:s:d:i:m:r:u:xtb"); + foreach ($options as $option => $value) { + switch ($option) { + case 'e': + $event = $value; + break; + case 's': + $subject = $value; + break; + case 'd': + $description = $value; + break; + case 'i': + $importance = strtok($value,' '); + $overrule = strtok(' '); + break; + case 'm': + $message = $value; + break; + case 'r': + $recipients = $value; + break; + case 'x': + $ticket = 'ticket'; + break; + case 't': + $mailtest = true; + break; + case 'b': + $noBrowser = true; + break; + case 'l': + $nginx = (array)@parse_ini_file('/var/local/emhttp/nginx.ini'); + $link = $value; + $fqdnlink = (strpos($link,"http") === 0) ? $link : ($nginx['NGINX_DEFAULTURL']??'').$link; + break; + case 'u': + $customFilename = $value; + break; + } + } + + if ($customFilename) { + $filename = safe_filename($customFilename); + } else { + // suffix length: _{timestamp}.notify = 1+10+7 = 18 chars. + $suffix = "_{$ticket}.notify"; + $max_name_len = 255 - strlen($suffix); + // sanitize event, truncating it to leave room for suffix + $clean_name = safe_filename($event, $max_name_len); + // construct filename with suffix (underscore separator matches safe_filename behavior) + $filename = "{$clean_name}{$suffix}"; + } + + $unread = "{$unread}/{$filename}"; + $archive = "{$archive}/{$filename}"; + if (file_exists($archive)) break; + $entity = $overrule===false ? $notify[$importance] : $overrule; + $cleanSubject = clean_subject($subject); + $archiveData = [ + 'timestamp' => $timestamp, + 'event' => $event, + 'subject' => $cleanSubject, + 'description' => $description, + 'importance' => $importance, + ]; + if ($message) $archiveData['message'] = str_replace('\n','
',$message); + if (!$mailtest) file_put_contents($archive, build_ini_string($archiveData)); + if (($entity & 1)==1 && !$mailtest && !$noBrowser) { + $unreadData = [ + 'timestamp' => $timestamp, + 'event' => $event, + 'subject' => $cleanSubject, + 'description' => $description, + 'importance' => $importance, + 'link' => $link, + ]; + file_put_contents($unread, build_ini_string($unreadData)); + } + if (($entity & 2)==2 || $mailtest) generate_email($event, $cleanSubject, str_replace('
','. ',$description), $importance, $message, $recipients, $fqdnlink); + if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg($cleanSubject)." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}}; + break; + +case 'get': + $output = []; + $json = []; + $files = glob("$unread/*.notify", GLOB_NOSORT); + usort($files, function($a,$b){return filemtime($a)-filemtime($b);}); + $i = 0; + foreach ($files as $file) { + $fields = file($file,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); + $time = true; + $output[$i]['file'] = basename($file); + $output[$i]['show'] = (fileperms($file) & 0x0FFF)==0400 ? 0 : 1; + foreach ($fields as $field) { + if (!$field) continue; + # limit the explode('=', …) used during reads to two pieces so values containing = remain intact + [$key,$val] = array_pad(explode('=', $field, 2),2,''); + if ($time) {$val = date($notify['date'].' '.$notify['time'], $val); $time = false;} + # unescape the value before emitting JSON, so the browser UI + # and any scripts calling `notify get` still see plain strings + $output[$i][trim($key)] = ini_decode_value($val); + } + $i++; + } + echo json_encode($output, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); + break; + +case 'archive': + if ($argc != 3) exit(usage()); + $file = $argv[2]; + if (strpos(realpath("$unread/$file"),$unread.'/')===0) @unlink("$unread/$file"); + break; +} + +exit(0); +?> \ No newline at end of file