<?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 . "/wh/core.inc.php"); $rg_wh_http_functions = array( 10000 => 'rg_wh_http_send', 10001 => 'rg_wh_http_send_one' ); rg_event_register_functions($rg_wh_http_functions); /* * Helper for rg_wh_send */ function rg_wh_http_send_one($db, $event) { rg_prof_start('wh_http_send_helper'); $wh = &$event['wh']; $info = &$wh['info']; rg_log_ml('wh_send_one: event: ' . print_r($event, TRUE)); if ($event['debug'] == 1) rg_log_ml('XXX DEBUG: wh[data]=' . print_r($wh['data'], TRUE)); $headers = array(); while (!empty($info['key'])) { if ($info['idata']['itype'] == 0) break; $headers[] = 'X-RocketGit-Signature: ' . hash_hmac('sha512', $wh['data'], $info['key']); break; } $c = curl_init($info['url']); curl_setopt($c, CURLOPT_POST, 1); curl_setopt($c, CURLOPT_POSTFIELDS, $wh['data']); 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($info['idata']['flags'], 'I')) curl_setopt($c, CURLOPT_SSL_VERIFYPEER, FALSE); if (strchr($info['idata']['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($info['idata']['client_cert'])) { rg_log('DEBUG: will provide client cert...'); $f = 'wh-' . $event['ui']['uid'] . '-' . $wh['id'] . '-client-' . $xid; $cert_file = rg_tmp_file($f, $info['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! curl_setopt($c, CURLOPT_SSLCERT, FALSE); } if (!empty($info['idata']['client_ca_cert'])) { $f = 'wh-' . $event['ui']['uid'] . '-' . $wh['id'] . '-ca-' . $xid; $ca_file = rg_tmp_file($f, $info['idata']['client_ca_cert']); if ($ca_file === FALSE) break; curl_setopt($c, CURLOPT_CAINFO, $ca_file); } else { curl_setopt($c, CURLOPT_CAINFO, FALSE); } $r = curl_exec($c); if ($err !== FALSE) { rewind($err); $xerr = @fread($err, 16 * 4096); fclose($err); rg_log_ml($xerr); } if ($r === FALSE) { rg_log('Cannot execute curl: ' . curl_error($c)); $_info = curl_getinfo($c); rg_log_ml('Debug: ' . print_r($_info, TRUE)); if ($event['debug'] == 1) rg_cache_set('DEBUG::' . $event['ui']['uid'] . '::webhooks::' . $info['opaque'] . '::' . $wh['id'], 'BAD', RG_SOCKET_NO_WAIT); break; } $xerr = &$r; rg_log_ml('Answer: ' . print_r($r, TRUE)); if ($event['debug'] == 1) rg_cache_set('DEBUG::' . $event['ui']['uid'] . '::webhooks::' . $info['opaque'] . '::' . $wh['id'], 'OK', RG_SOCKET_NO_WAIT); $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, $event['ui']['uid'], $wh['id'], substr($xerr, 0, 4096)); rg_prof_end('wh_http_send_helper'); return $ret; } /* * Generic function which will be called when a webhook must be posted */ function rg_wh_http_send($db, $event) { rg_prof_start('wh_http_send'); rg_log_ml('wh_http_send: event: ' . print_r($event, TRUE)); // First, get the list of hooks $r = rg_wh_list($db, $event['ui']['uid']); if ($r['ok'] != 1) return FALSE; // Filter them by repo_id $real_list = array(); foreach ($r['list'] as $id => $info) { if (($info['repo_id'] > 0) && ($event['ri']['repo_id'] != $info['repo_id'])) { rg_log('hook is not for this repo'); continue; } // If the web hook does not contain our type, skip it if (!strchr($info['events'], $event['wh_event'])) { rg_log($event['wh_event'] . ' is not present in ' . $info['events']); continue; } $real_list[] = $id; } // Something to do? if (empty($real_list)) return array(); $cache = array(); $wh = array(); $ret = array(); foreach ($real_list as $id) { $wh['info'] = $r['list'][$id]; $wh['id'] = $id; if (!isset($wh['info']['idata']['itype'])) { rg_log_ml('wh[info]: ' . print_r($wh['info'], TRUE)); rg_internal_error('DEBUG: itype is not present'); } $type = $wh['info']['idata']['itype']; if (!isset($cache[$type])) { switch ($type) { case 0: // http post $cache[$type] = &$event['wh_data']; break; case 1: // php serialize $cache[$type] = serialize($event['wh_data']); break; default: rg_log('Unknown type ' . $type . '!'); $cache[$type] = ''; break; } } $wh['data'] = $cache[$type]; $x = $event; $x['category'] = 10001; $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' ); /* * 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; } $rg_wh_http_flags = array( 'I' => 'Do not verify the server certificate', 'H' => 'Do not verify the server hostname' ); /* * Generates flags list */ function rg_wh_http_check_flags($flags) { global $rg_wh_http_flags; $ret = ''; $br = ''; foreach ($rg_wh_http_flags as $id => $name) { $add = ''; if (strchr($flags, $id)) $add = ' checked="checked"'; $ret .= $br . '<input type="checkbox" name="wh::idata::flags[' . $id . ']"' . ' id="flags-' . $id . '"' . $add . ' />' . "\n" . '<label for="flags-' . $id . '">' . $name . '</label>'; $br = '<br />' . "\n"; } return $ret; } /* * Generates a flags list as text */ function rg_wh_http_flags($flags) { global $rg_wh_http_flags; $a = array(); foreach ($rg_wh_http_flags as $id => $name) { if (strchr($flags, $id)) $a[] = $name; } return implode(', ', $a); } /* * Some cosmetics applied to a webhook */ function rg_wh_http_cosmetic(&$row) { // TODO DEBUG remove the following 2 lines if (!isset($row['idata']['itype'])) $row['idata']['itype'] = 0; $row['idata']['itype_text'] = rg_wh_http_itype($row['idata']['itype']); $row['idata']['flags_text'] = rg_wh_http_flags($row['idata']['flags']); $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:private'] = rg_template( 'user/settings/wh/http/show.html', $row['idata'], TRUE /*xss*/); } /* * Fill private data based on parameters passed */ function rg_wh_http_fill_vars(&$rg) { $a = array(); $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['flags'] = rg_var_a2s('wh::idata::flags'); $rg['wh']['idata'] = $a; } /* * Validate parameters passed */ function rg_wh_http_validate_vars($rg, &$errmsg) { global $rg_wh_http_itypes; global $rg_wh_http_flags; $a = $rg['wh']; $ret = FALSE; while (1) { if ((strncasecmp($a['url'], 'http', 4) != 0) && (strncasecmp($a['url'], 'https', 5) != 0)) { $errmsg[] = rg_template('user/settings/wh/http/inv_proto.txt', $rg, TRUE /*xss*/); break; } if (!isset($rg_wh_http_itypes[$a['idata']['itype']])) { $errmsg[] = rg_template('user/settings/wh/http/inv_type.txt', $rg, TRUE /*xss*/); break; } $all_ok = TRUE; $len = strlen($a['idata']['flags']); for ($i = 0; $i < $len; $i++) { $f = $a['idata']['flags'][$i]; if (!isset($rg_wh_http_flags[$f])) { $all_ok = FALSE; $errmsg[] = rg_template('user/settings/wh/http/inv_flag.txt', $rg, TRUE /*xss*/); break; } } if (!$all_ok) break; $ret = TRUE; break; } return $ret; } /* * Transfers to $rg the custom parameters - used when showing the form */ function rg_wh_http_add_form(&$rg) { // TODO DEBUG remove this if (!isset($rg['wh']['idata']['itype'])) $rg['wh']['idata']['itype'] = 0; $rg['HTML:select_itype'] = rg_wh_http_select_itype($rg['wh']['idata']['itype']); $rg['HTML:check_flags'] = rg_wh_http_check_flags($rg['wh']['idata']['flags']); $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*/); } /* * Loads default paras for a form */ function rg_wh_http_default_paras(&$rg) { $a = array(); $a['itype'] = 0; $a['flags'] = ''; $a['client_cert'] = ''; $a['client_ca_cert'] = ''; $rg['wh']['idata'] = $a; } $rg_wh_plugins['http'] = array( 'htype' => 'http', 'description' => 'HTTP[S]', 'cosmetic' => 'rg_wh_http_cosmetic', 'html_form' => 'rg_wh_http_form', 'fill_vars' => 'rg_wh_http_fill_vars', 'validate_vars' => 'rg_wh_http_validate_vars', 'add_form' => 'rg_wh_http_add_form', 'default_paras' => 'rg_wh_http_default_paras', 'fill_hints' => 'rg_wh_http_fill_hints' ); ?>