<?php require_once($INC . "/util.inc.php"); require_once($INC . "/log.inc.php"); require_once($INC . "/sql.inc.php"); require_once($INC . "/prof.inc.php"); require_once($INC . "/events.inc.php"); require_once($INC . "/wh/core.inc.php"); $rg_wh_http_functions = array( 'wh_http_send' => 'rg_wh_http_send', 'wh_http_send_one' => 'rg_wh_http_send_one' ); rg_event_register_functions($rg_wh_http_functions); /* * Helper for rg_wh_http_send */ function rg_wh_http_send_helper($db, &$ev) { rg_prof_start('wh_http_send_helper'); rg_log_enter('wh_http_send_helper'); rg_wh_call_callback($ev['wh']['htype'], $ev['wh']['hsubtype'], 'send_pre', $xret_junk, $ev, $junk); // replace ##tags## rg_wh_replace_tags($ev); if ($ev['debug'] == 1) rg_log_ml('wh_http_send_one: event[wh][idata]: ' . print_r($ev['wh']['idata'], TRUE)); $headers = array(); if (!empty($ev['wh']['idata']['key']) && ($ev['wh']['idata']['itype'] != 0)) $headers[] = 'X-RocketGit-Signature: ' . hash_hmac('sha512', $ev['wh']['idata']['final'], $ev['wh']['idata']['key']); if (!empty($ev['wh']['idata']['content_type'])) $headers[] = 'Content-Type: ' . $ev['wh']['idata']['content_type']; $c = curl_init($ev['wh']['idata']['url']); curl_setopt($c, CURLOPT_POST, 1); curl_setopt($c, CURLOPT_POSTFIELDS, $ev['wh']['idata']['final']); curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($c, CURLOPT_HEADER, 1); curl_setopt($c, CURLOPT_HTTPHEADER, $headers); curl_setopt($c, CURLOPT_USERAGENT, 'RocketGit WebHook'); curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($c, CURLOPT_ENCODING, ''); // => use all methods curl_setopt($c, CURLOPT_VERBOSE, TRUE); curl_setopt($c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); //curl_setopt($c, CURLOPT_CERTINFO, TRUE); // Else, a second wh, without client cert can use the connection: curl_setopt($c, CURLOPT_FORBID_REUSE, TRUE); $err = @fopen('php://temp', 'w'); if ($err !== FALSE) curl_setopt($c, CURLOPT_STDERR, $err); if (strchr($ev['wh']['flags'], 'I')) curl_setopt($c, CURLOPT_SSL_VERIFYPEER, FALSE); if (strchr($ev['wh']['flags'], 'H')) curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 0); // TODO verify 0 is a good value (default 2) $ret = FALSE; $cert_file = FALSE; $ca_file = FALSE; while (1) { $xid = rg_id(8); if (!empty($ev['wh']['idata']['client_cert'])) { rg_log('DEBUG: will provide client cert...'); $f = 'wh-' . $ev['ui']['uid'] . '-' . $ev['wh']['id'] . '-client-' . $xid; $cert_file = rg_tmp_file($f, $ev['wh']['idata']['client_cert']); if ($cert_file === FALSE) break; curl_setopt($c, CURLOPT_SSLCERT, $cert_file); } else { rg_log('DEBUG: will NOT provide client cert...'); // TODO: Somehow, without next line, the cert is still sent! // TODO: With next line, curl tries to find the empty file name //curl_setopt($c, CURLOPT_SSLCERT, FALSE); // TODO: Without it works fine, at least on F27! } if (!empty($ev['wh']['idata']['client_ca_cert'])) { $f = 'wh-' . $ev['ui']['uid'] . '-' . $ev['wh']['id'] . '-ca-' . $xid; $ca_file = rg_tmp_file($f, $ev['wh']['idata']['client_ca_cert']); if ($ca_file === FALSE) break; curl_setopt($c, CURLOPT_CAINFO, $ca_file); } $xerr = 'Date (UTC): ' . gmdate('Y-m-d H:i') . "\n"; $xerr .= "\n"; $r = @curl_exec($c); if ($r == FALSE) { $ev['wh']['out']['headers'] = ''; $ev['wh']['out']['body'] = ''; } else { $_x = explode("\r\n\r\n", $r, 2); $ev['wh']['out']['headers'] = $_x[0]; $ev['wh']['out']['body'] = $_x[1]; } if ($err !== FALSE) { rewind($err); $xerr .= @fread($err, 16 * 4096); fclose($err); rg_log_ml('xerr: ' . $xerr); } $ev['wh']['out']['curl_info'] = @curl_getinfo($c); rg_log('curl_getinfo: ' . rg_array2string($ev['wh']['out']['curl_info'])); if ($r === FALSE) { rg_log('Cannot execute curl: ' . curl_error($c) . '.'); $debug_value = 'BAD'; break; } rg_log_ml('DEBUG: Answer: ' . print_r($r, TRUE)); $xerr .= $r; $debug_value = 'OK'; $ret = array(); break; } curl_close($c); if ($cert_file !== FALSE) @unlink($cert_file); if ($ca_file !== FALSE) @unlink($ca_file); rg_wh_set_last_output($db, $ev['ui']['uid'], $ev['wh']['id'], $xerr); if ($ev['debug'] == 1) rg_cache_set('DEBUG::' . $ev['ui']['uid'] . '::webhooks::' . $ev['wh']['idata']['opaque'] . '::' . $ev['wh']['id'], $debug_value, RG_SOCKET_NO_WAIT); rg_log_exit(); rg_prof_end('wh_http_send_helper'); return $ret; } /* * Helper for rg_wh_http_send */ function rg_wh_http_send_one($db, $ev) { rg_prof_start('wh_http_send_one'); while (1) { $r = rg_wh_call_callback($ev['wh']['htype'], $ev['wh']['hsubtype'], 'send', $ret, $db, $ev); if ($r === TRUE) // TRUE = "callback called" break; $ret = rg_wh_http_send_helper($db, $ev); break; } rg_prof_end('wh_http_send_one'); return $ret; } /* * Generic function which will be called when a webhook must be run */ function rg_wh_http_send($db, $ev) { rg_prof_start('wh_http_send'); //rg_log_ml('wh_http_send: event: ' . print_r($ev, TRUE)); // First, get the list of hooks $r = rg_wh_list($db, $ev['ui']['uid']); if ($r['ok'] != 1) return FALSE; $cache = array(); $ret = array(); // Filter foreach ($r['list'] as $id => $wh) { $error = FALSE; if (strcmp($wh['htype'], 'http') != 0) continue; if (strchr($wh['flags'], 'D')) continue; // If the web hook does not contain our type, skip it if (!strchr($wh['idata']['events'], $ev['wh_event'])) { rg_log('DEBUG: ' . $ev['wh_event'] . ' is not present in ' . $wh['idata']['events']); continue; } if (isset($ev['ri']['name']) && !rg_wh_repo_match($wh['repo'], $ev['ri']['name'])) { rg_log('DEBUG: hook is not for repo ' . $ev['ri']['name']); continue; } if (isset($ev['refname']) && !rg_repo_compare_refs($wh['refname'], $ev['refname'])) { rg_log('DEBUG: hook is not for ref ' . $ev['refname']); continue; } $itype = $wh['idata']['itype']; if (isset($cache[$itype])) { $wh['idata']['final'] = $cache[$itype]; } else { switch ($itype) { case 0: // http post $cache[$itype] = rg_array_flat('', $ev); $wh['idata']['final'] = $cache[$itype]; break; case 1: // php serialize $cache[$itype] = serialize($ev); $wh['idata']['final'] = $cache[$itype]; break; case 2: // custom body (cannot be cached) $wh['idata']['final'] = $wh['idata']['custom_body']; break; default: rg_log('Unknown type ' . $itype . '!'); rg_internal_error('itype is unknown: ' . $wh['idata']['itype']); $error = TRUE; break; } } if ($error) continue; $x = $ev; $x['category'] = 'wh_http_send_one'; $x['wh'] = $wh; $ret[] = $x; } rg_prof_end('wh_http_send'); return $ret; } $rg_wh_http_itypes = array( 0 => 'HTTP (application/x-www-form-urlencoded)', 1 => 'PHP serialize', 2 => 'Custom (use custom body field)' ); /* * Transforms a type into HTML select */ function rg_wh_http_select_itype($itype) { global $rg_wh_http_itypes; $ret = '<select name="wh::idata::itype" id="itype">'; foreach ($rg_wh_http_itypes as $_itype => $name) { $add = ''; if ($_itype == $itype) $add = ' selected'; $ret .= '<option value="' . $_itype . '"' . $add . '>' . $name . '</option>' . "\n"; } $ret .= '</select>' . "\n"; return $ret; } /* * Returns type text based on id */ function rg_wh_http_itype($itype) { global $rg_wh_http_itypes; foreach ($rg_wh_http_itypes as $_itype => $name) if ($_itype == $itype) return $name; } /* * Some cosmetics applied to a webhook */ function rg_wh_http_cosmetic_pre(&$row) { rg_log('DEBUG: wh_http_cosmetic:' . rg_array2string($row)); $row['idata']['itype_text'] = rg_wh_http_itype($row['idata']['itype']); $row['idata']['HTML:client_cert_short'] = rg_xss_safe(rg_cert_short($row['idata']['client_cert'])); $row['idata']['HTML:client_ca_cert_short'] = rg_xss_safe(rg_cert_short($row['idata']['client_ca_cert'])); $row['idata']['HTML:custom_body_nlbr'] = nl2br(rg_xss_safe($row['idata']['custom_body'])); } /* * Some cosmetics applied to a webhook */ function rg_wh_http_cosmetic_post(&$row) { rg_log('DEBUG: wh_http_cosmetic_post:' . rg_array2string($row)); $row['idata']['HTML:private'] = rg_template( 'user/settings/wh/http/show.html', $row, TRUE /*xss*/); } /* * Fill private data based on parameters passed */ function rg_wh_http_fill_vars(&$rg) { $a = $rg['wh']['idata']; $a['url'] = trim(rg_var_str('wh::idata::url')); $a['itype'] = rg_var_uint('wh::idata::itype'); $a['client_cert'] = trim(rg_var_str('wh::idata::client_cert')); $a['client_ca_cert'] = trim(rg_var_str('wh::idata::client_ca_cert')); $a['url_example'] = trim(rg_var_str('wh::idata::url_example')); $a['url'] = trim(rg_var_str('wh::idata::url')); $a['opaque'] = trim(rg_var_str('wh::idata::opaque')); $a['key'] = trim(rg_var_str('wh::idata::key')); $a['content_type'] = trim(rg_var_str('wh::idata::content_type')); $a['custom_body'] = trim(rg_var_str('wh::idata::custom_body')); $rg['wh']['idata'] = $a; //rg_log_ml('DEBUG: AFTTER wh_http_fill_vars: ' . print_r($a, TRUE)); } /* * Validate parameters passed */ function rg_wh_http_validate_vars($rg, &$errmsg) { global $rg_wh_http_itypes; $ret = FALSE; while (1) { if (empty($rg['wh']['idata']['url'])) { $errmsg[] = rg_template('user/settings/wh/http/inv_url.html', $rg, TRUE /*xss*/); break; } if ((strncasecmp($rg['wh']['idata']['url'], 'http', 4) != 0) && (strncasecmp($rg['wh']['idata']['url'], 'https', 5) != 0)) { $errmsg[] = rg_template('user/settings/wh/http/inv_proto.txt', $rg, TRUE /*xss*/); break; } if (!isset($rg_wh_http_itypes[$rg['wh']['idata']['itype']])) { $errmsg[] = rg_template('user/settings/wh/http/inv_type.txt', $rg, TRUE /*xss*/); break; } $ret = TRUE; break; } return $ret; } /* * Transfers to $rg the custom parameters - used when showing the form */ function rg_wh_http_add_form_pre($db, &$rg) { $a = $rg['wh']['idata']; $a['show_sign_key'] = 0; if (!isset($a['url'])) $a['url'] = ''; if (!isset($a['itype'])) $a['itype'] = 0; if (!isset($a['itype_force'])) $a['itype_force'] = 0; if (!isset($a['client_cert'])) $a['client_cert'] = ''; if (!isset($a['client_ca_cert'])) $a['client_ca_cert'] = ''; if (!isset($a['opaque'])) $a['opaque'] = ''; if (!isset($a['key'])) $a['key'] = ''; if (!isset($a['content_type'])) $a['content_type'] = ''; if (!isset($a['custom_body'])) $a['custom_body'] = ''; $rg['HTML:select_itype'] = rg_wh_http_select_itype($a['itype']); $rg['wh']['idata'] = $a; } /* * Transfers to $rg the custom parameters - used when showing the form */ function rg_wh_http_add_form_post($db, &$rg) { rg_log('DEBUG: wh_http_add_form_post'); $rg['HTML:custom_form'] = rg_template('user/settings/wh/http/form.html', $rg, TRUE /*xss*/); } /* * Add custom hints */ function rg_wh_http_fill_hints($rg, &$hints) { $hints[]['HTML:hint'] = rg_template('user/settings/wh/http/hints.html', $rg, TRUE /*xss*/); } // TODO: duplicate 'http' string! $info = array( 'htype' => 'http', 'description' => 'HTTP[S] - Calls any URL you want', 'cb' => array( 'cosmetic_pre' => 'rg_wh_http_cosmetic_pre', 'cosmetic_post' => 'rg_wh_http_cosmetic_post', 'fill_vars' => 'rg_wh_http_fill_vars', 'validate_vars' => 'rg_wh_http_validate_vars', 'add_form_pre' => 'rg_wh_http_add_form_pre', 'add_form_post' => 'rg_wh_http_add_form_post', 'fill_hints' => 'rg_wh_http_fill_hints' ), 'have_events' => TRUE, 'flags' => array( 'I' => 'Do not verify the server certificate (not recommended)', 'H' => 'Do not verify the server hostname (not recommended)' ) ); rg_wh_register_type('http', $info); // Generic must be first, then, order them alphabetically require_once(__DIR__ . '/http/generic.inc.php'); require_once(__DIR__ . '/http/matrix.inc.php'); require_once(__DIR__ . '/http/slack.inc.php');