/** * Determines the intersection between an IP (with optional prefix) and a * CIDR block. * * The IP will be checked against the CIDR block given and will either be * inside or outside the CIDR completely, or partially. * * NOTE: The caller should explicitly check against the INTERSECT_* * constants because this method will return a value > 1 even for partial * matches. * * @param mixed $ip The IP/cidr to match * @param mixed $cidr The CIDR block to match within * @return integer Returns an INTERSECT_* constant * @throws \InvalidArgumentException if either $ip or $cidr is invalid */ public static function cidr_intersect($ip, $cidr) { // use fixed length HEX strings so we can easily do STRING comparisons // instead of using slower bccomp() math. $map = function ($v) { return sprintf("%032s", IP::inet_ptoh($v)); }; list($lo, $hi) = array_map($map, CIDR::cidr_to_range($ip)); list($min, $max) = array_map($map, CIDR::cidr_to_range($cidr)); /** visualization of logic used below lo-hi = $ip to check min-max = $cidr block being checked against --- --- --- lo --- --- hi --- --- --- --- --- IP/prefix to check --- min --- --- max --- --- --- --- --- --- --- Partial "LOW" match --- --- --- --- --- min --- --- max --- --- --- Partial "HIGH" match --- --- --- --- min max --- --- --- --- --- --- No match "NO" --- --- --- --- --- --- --- --- min --- max --- No match "NO" min --- max --- --- --- --- --- --- --- --- --- No match "NO" --- --- min --- --- --- --- max --- --- --- --- Full match "YES" */ // IP is exact match or completely inside the CIDR block if ($lo >= $min and $hi <= $max) { return self::INTERSECT_YES; } // IP is completely outside the CIDR block if ($max < $lo or $min > $hi) { return self::INTERSECT_NO; } // @todo is it useful to return LOW/HIGH partial matches? // IP matches the lower end if ($max <= $hi and $min <= $lo) { return self::INTERSECT_LOW; } // IP matches the higher end if ($min >= $lo and $max >= $hi) { return self::INTERSECT_HIGH; } return self::INTERSECT_NO; }
/** * */ public function putMod(Request $request, Board $board, $post) { // Validate the request parameters. if (!($post = $this->validatePost($board, $post)) instanceof Post) { // If the response isn't a Post, it's a redirect or error. // Return the message. return $post; } // Take trailing arguments, // compare them against a list of real actions, // intersect the liss to find the true commands. $actions = ["delete", "ban", "all", "global"]; $argList = func_get_args(); $modActions = array_intersect($actions, array_splice($argList, 2)); sort($modActions); $ban = in_array("ban", $modActions); $delete = in_array("delete", $modActions); $all = in_array("all", $modActions); $global = in_array("global", $modActions); if (!$ban) { return abort(404); } $validator = Validator::make(Input::all(), ['raw_ip' => 'required|boolean', 'ban_ip' => 'required_if:raw_ip,true|ip', 'ban_ip_range' => 'required|between:0,128', 'justification' => 'max:255', 'expires_days' => 'required|integer|min:0|max:' . $this->option('banMaxLength'), 'expires_hours' => 'required|integer|min:0|max:23', 'expires_minutes' => 'required|integer|min:0|max:59']); if (!$validator->passes()) { return redirect()->back()->withInput(Input::all())->withErrors($validator->errors()); } $banLengthStr = []; $expiresDays = Input::get('expires_days'); $expiresHours = Input::get('expires_hours'); $expiresMinutes = Input::get('expires_minutes'); if ($expiresDays > 0) { $banLengthStr[] = "{$expiresDays}d"; } if ($expiresHours > 0) { $banLengthStr[] = "{$expiresHours}h"; } if ($expiresMinutes > 0) { $banLengthStr[] = "{$expiresMinutes}m"; } if ($expiresDays == 0 && $expiresHours == 0 && $expiresMinutes == 0) { $banLengthStr[] = "Ø"; } $banLengthStr = implode($banLengthStr, " "); // If we're banning without the ability to view IP addresses, we will get our address directly from the post in human-readable format. $banIpAddr = $this->user->canViewRawIP() ? Input::get('ban_ip') : $post->getAuthorIpAsString(); // The CIDR is passed from our post parameters. By default, it is 32/128 for IPv4/IPv6 respectively. $banCidr = Input::get('ban_ip_range'); // This generates a range from start to finish. I.E. 192.168.1.3/22 becomes [192.168.0.0, 192.168.3.255]. // If we just pass the CDIR into the construct, we get 192.168.1.3-129.168.3.255 for some reason. $banCidrRange = CIDR::cidr_to_range("{$banIpAddr}/{$banCidr}"); // We then pass this range into the construct method. $banIp = new CIDR($banCidrRange[0], $banCidrRange[1]); $ban = new Ban(); $ban->ban_ip_start = inet_pton($banIp->getStart()); $ban->ban_ip_end = inet_pton($banIp->getEnd()); $ban->seen = false; $ban->created_at = $ban->freshTimestamp(); $ban->updated_at = clone $ban->created_at; $ban->expires_at = clone $ban->created_at; $ban->expires_at->addDays($expiresDays); $ban->expires_at->addHours($expiresHours); $ban->expires_at->addMinutes($expiresMinutes); $ban->mod_id = $this->user->user_id; $ban->post_id = $post->post_id; $ban->ban_reason_id = null; $ban->justification = Input::get('justification'); if ($global) { if ($ban && !$this->user->canBanGlobally() || $delete && !$this->user->canDeleteGlobally()) { return abort(403); } if ($ban) { $ban->board_uri = null; $ban->save(); } $this->log('log.post.ban.global', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "ip" => $post->getAuthorIpAsString(), "justification" => $ban->justification, "time" => $banLengthStr]); if ($delete) { $posts = Post::ipBinary($banIp); $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => $posts->count()]); $posts->delete(); return redirect($board->board_uri); } } else { if ($ban && !$board->canBan($this->user) || $delete && !$board->canDelete($this->user)) { return abort(403); } if ($ban) { $ban->board_uri = $post->board_uri; $ban->save(); } $this->log('log.post.ban.local', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "ip" => $post->getAuthorIpAsString(), "justification" => $ban->justification, "time" => $banLengthStr]); if ($delete) { if ($all) { $posts = Post::ipBinary($banIp)->where('board_uri', $board->board_uri); $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => $posts->count()]); $posts->delete(); return redirect($board->board_uri); } else { $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => 1]); $post->delete(); if ($post->reply_to) { return redirect("{$post->board_uri}/thread/{$post->op->board_id}"); } else { return redirect($board->board_uri); } } } } Event::fire(new PostWasBanned($post)); Event::fire(new PostWasModerated($post, $this->user)); if ($post->reply_to) { return redirect("{$post->board_uri}/thread/{$post->op->board_id}#{$post->board_id}"); } else { return redirect("{$post->board_uri}/thread/{$post->board_id}"); } }
/** * Converts an IPv4 or IPv6 CIDR block into its range. * * @static * @param string $cidr CIDR block or IP address string. * @param integer|null $bits If /bits is not specified on string they can be passed via this parameter instead.cidr_intersect * @return array A 2 element array with the low, high range */ public static function cidr_to_range($cidr, $bits = null) { if ($cidr instanceof static) { return parent::cidr_to_range($cidr->getCidr(), $bits); } return parent::cidr_to_range($cidr, $bits); }