exitIfFromSelf(); $file = $this->argument('file'); $this->parser = $this->getParser($file); $recipients = $this->getRecipients(); // Divide the size of the email by the number of recipients (excluding any unsubscribe recipients) to prevent it being added multiple times. $recipientCount = $recipients->where('domain', '!=', 'unsubscribe.'.config('anonaddy.domain'))->count(); $this->size = $this->option('size') / ($recipientCount ? $recipientCount : 1); foreach ($recipients as $key => $recipient) { $displayTo = $this->parser->getAddresses('to')[$key]['display'] ?? null; $parentDomain = collect(config('anonaddy.all_domains')) ->filter(function ($name) use ($recipient) { return Str::endsWith($recipient['domain'], $name); }) ->first(); if ($parentDomain) { $subdomain = substr($recipient['domain'], 0, strrpos($recipient['domain'], '.'.$parentDomain)); if ($subdomain === 'unsubscribe') { $this->handleUnsubscribe($recipient); continue; } // Check if this is an additional username. if ($additionalUsername = AdditionalUsername::where('username', $subdomain)->first()) { $user = $additionalUsername->user; $aliasable = $additionalUsername; } else { $user = User::where('username', $subdomain)->first(); } } if (!isset($user)) { // Check if this is a custom domain. if ($customDomain = Domain::where('domain', $recipient['domain'])->first()) { $user = $customDomain->user; $aliasable = $customDomain; } // Check if this is a uuid generated alias. if ($alias = Alias::find($recipient['local_part'])) { $user = $alias->user; } elseif ($recipient['domain'] === $parentDomain && !empty(config('anonaddy.admin_username'))) { $user = User::where('username', config('anonaddy.admin_username'))->first(); } } // If there is still no user or the user has no verified default recipient then continue. if (!isset($user) || !$user->hasVerifiedDefaultRecipient()) { continue; } $this->checkBandwidthLimit($user); $this->checkRateLimit($user); // Check whether this email is a reply or a new email to be forwarded. if ($recipient['extension'] === sha1(config('anonaddy.secret').$displayTo)) { $this->handleReply($user, $recipient, $displayTo); } else { $this->handleForward($user, $recipient, $aliasable ?? null); } } } catch (\Exception $e) { report($e); $this->error('4.3.0 An error has occurred, please try again later.'); exit(1); } } protected function handleUnsubscribe($recipient) { $alias = Alias::find($recipient['local_part']); if (!is_null($alias) && $alias->user->isVerifiedRecipient($this->option('sender'))) { $alias->deactivate(); } } protected function handleReply($user, $recipient, $displayTo) { $alias = $user->aliases()->where('email', $recipient['local_part'] . '@' . $recipient['domain'])->first(); if (!is_null($alias) && filter_var($displayTo, FILTER_VALIDATE_EMAIL)) { $emailData = new EmailData($this->parser); $message = new ReplyToEmail($user, $alias, $emailData); Mail::to($displayTo)->queue($message); if (!Mail::failures()) { $alias->increment('emails_replied'); $user->bandwidth += $this->size; $user->save(); } } } protected function handleForward($user, $recipient, $aliasable) { $alias = $user->aliases()->firstOrNew([ 'email' => $recipient['local_part'] . '@' . $recipient['domain'], 'local_part' => $recipient['local_part'], 'domain' => $recipient['domain'], 'aliasable_id' => $aliasable->id ?? null, 'aliasable_type' => $aliasable ? 'App\\'.class_basename($aliasable) : null ]); if (!isset($alias->id)) { // This is a new alias. if ($user->hasExceededNewAliasLimit()) { $this->error('4.2.1 New aliases per hour limit exceeded for user.'); exit(1); } if ($recipient['extension'] !== '') { $alias->extension = $recipient['extension']; $keys = explode('.', $recipient['extension']); $recipientIds = $user ->recipients() ->oldest() ->get() ->filter(function ($item, $key) use ($keys) { return in_array($key+1, $keys) && !is_null($item['email_verified_at']); }) ->pluck('id') ->take(10) ->toArray(); } } $alias->save(); $alias->refresh(); if (isset($recipientIds)) { $alias->recipients()->sync($recipientIds); } $emailData = new EmailData($this->parser); $alias->verifiedRecipientsOrDefault()->each(function ($recipient) use ($alias, $emailData) { $message = new ForwardEmail($alias, $emailData, $recipient->should_encrypt ? $recipient->fingerprint : null); Mail::to($recipient->email)->queue($message); }); if (!Mail::failures()) { $alias->increment('emails_forwarded'); $user->bandwidth += $this->size; $user->save(); } } protected function checkBandwidthLimit($user) { if ($user->hasReachedBandwidthLimit()) { $this->error('4.2.1 Bandwidth limit exceeded for user. Please try again later.'); exit(1); } if ($user->nearBandwidthLimit() && ! Cache::has("user:{$user->username}:near-bandwidth")) { $user->notify(new NearBandwidthLimit()); Cache::put("user:{$user->username}:near-bandwidth", now()->toDateTimeString(), now()->addDay()); } } protected function checkRateLimit($user) { Redis::throttle("user:{$user->username}:limit:emails") ->allow(config('anonaddy.limit')) ->every(3600) ->then( function () { }, function () { $this->error('4.2.1 Rate limit exceeded for user. Please try again later.'); exit(1); } ); } protected function getRecipients() { return collect($this->option('recipient'))->map(function ($item, $key) { return [ 'email' => $item, 'local_part' => strtolower($this->option('local_part')[$key]), 'extension' => $this->option('extension')[$key], 'domain' => strtolower($this->option('domain')[$key]) ]; }); } protected function getParser($file) { $parser = new Parser; if ($file == 'stream') { $fd = fopen('php://stdin', 'r'); $this->rawEmail = ''; while (!feof($fd)) { $this->rawEmail .= fread($fd, 1024); } fclose($fd); $parser->setText($this->rawEmail); } else { $parser->setPath($file); } return $parser; } protected function exitIfFromSelf() { // To prevent recipient alias infinite nested looping. if (in_array($this->option('sender'), [config('mail.from.address'), config('anonaddy.return_path')])) { exit(0); } } }