DKIM podpisy pro důveryhodnější e-mail

16. 4. 2015
Doba čtení: 7 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
V druhém díle seriálu o reputačních systémech popíšeme standard DKIM, jehož cílem je prokázat pravost e-mailových zpráv. Používá k tomu principy elektronického podpisu, jehož důvěryhodnost se zajišťuje protokolem DNS. Vyznačuje se snadným nasazením bez rizika narušení doručitelnosti elektronické pošty.

O standardu DomainKeys Identified Mail jsme již před časem psali, detailní popis vyšel také již poměrně dávno na Lupě. Jedná se o systém zabezpečení obsahu e-mailových zpráv před změnou prostřednictvím elektronického podpisu. Od zavedených systémů PGP a S/MIME se liší především tím, že podepisování může provádět kterýkoli článek e-mailové infrastruktury, takže není nezbytně nutné vyžadovat používání této technologie po uživatelích.

Také se zde nepoužívá ani hierarchie veřejných klíčů, ani síť důvěry; veřejný klíč potřebný k ověření podpisu se jednoduše umístí do DNS v podobě TXT záznamu. Ačkoli se zde také používá obecný typ záznamu TXT, kolizi s jinými účely se brání předepsaným prefixem _domainkey, za který je připojena doména, která má být zdrojem podpisu. Aby bylo možné klíče hladce měnit a/nebo používat různé klíče v různých částech organizace, je každý DNS záznam s klíčem uvozen tzv. selektorem, což může být naprosto libovolný řetězec, splňující požadavky na platné DNS jméno.

Nejde o náhradu SPF

Ačkoli se zdá, že účel, ke kterému DKIM existuje, je podobný účelu SPF, které jsme popsali minule, není tomu tak. Připomeňme, že SPF řeší pouze autorizaci e-mailového serveru, či obálkové adresy odesílatele, která je předávána v průběhu SMTP komunikace. SPF nijak nezkoumá ani hlavičky, ani vlastní obsah e-mailové zprávy. Proti šíření nejrůznějších podvržených zpráv, sloužících nejčastěji phishingu, které mají v pořádku obálkovou adresu odesílatele, žádným způsobem nepomáhá.

DKIM naopak autorizuje obsah zprávy, zcela bez ohledu na to, jakým způsobem byla zpráva doručena. Samotný standard DKIM také na rozdíl od SPF nepředepisuje žádnou politiku, která by stanovovala co se má stát se zprávami, jejichž elektronický podpis nevyhoví. Dokonce jeden z cílů DKIMu vyžaduje, aby zprávy s nevalidním DKIM podpisem byly hodnoceny stejně jako zprávy bez jakéhokoli podpisu. To umožňuje DKIM podepisování nasadit beze strachu ze zhoršení spolehlivosti e-mailového systému.

Podepisování snadno a rychle s OpenDKIM

Pro vlastní podepisování a ověřování podpisu je možné použít nástroj OpenDKIM. Ten se chová jako milter, který lze snadno napojit na současné poštovní servery. Nejprve zvolíme selektor (nemáte-li fantazii, letopočet bude dobrou volbou) a vygenerujeme klíč pro podepisování:

$ opendkim-genkey -s 2015 -b 2048 

V aktuálním adresáři vzniknou dva soubory s názvem <selektor>.private a <selektor>.txt. První obsahuje privátní klíč, který je potřeba nakopírovat do /etc/dkim, druhý soubor pak obsahuje DNS záznam, který je potřeba vložit do domény, jejímž jménem má být podepisováno:

2015._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIB…QAB" ; ----- DKIM key 2015 for example.com 

Klíč pro podepisování je následně nutné nastavit v konfiguračním souboru /etc/opendkim.conf spolu s nezbytnými nastaveními pro integraci s Postfixem na Debianu:

Domain                  example.cz
KeyFile                 /etc/dkim/2015.private
Selector                2015
Socket                  local:/var/spool/postfix/opendkim/opendkim.sock
Umask                   002 

Na straně Postfixu pak stačí nastavit správně práva a zařadit milter jak pro příchozí SMTP zprávy, tak pro lokálně odeslané zprávy:

# mkdir /var/spool/postfix/opendkim
# chown opendkim:opendkim /var/spool/postfix/opendkim
# gpasswd -a postfix opendkim
# postconf -e smtpd_milters=unix:opendkim/opendkim.sock
# postconf -e non_smtpd_milters=unix:opendkim/opendkim.sock
# service postfix restart 

Od té chvíle OpenDKIM podepisuje zprávy z domény, pro kterou má klíč, a validuje podpisy pro všechny ostatní. Je možné také nastavit více klíčů pro různé domény a podepisovat správným klíčem v závislosti na doméně odesílatele. Další podrobnosti jsou k nalezení v manuálu opendkim.conf.

Kanonizace a přepodepisování hlaviček

Při konfiguraci podepisovacího softwaru jsou k dispozici další volby, které by neměly uniknout administrátorově pozornosti. Tou první je kanonizace, tedy jakási předúprava hlaviček a těla zprávy před provedením vlastního elektronického podpisu. Standardní nastavení na hodnotu simple/simple, žádnou úpravu neprovádí a tak k poškození elektronického podpisu dojde i sémanticky nevýznamnou změnou hlaviček (úprava velikosti písmen názvů hlaviček) či obsahu (přidání či odebrání bílých mezer). Ačkoli asi panuje všeobecná shoda na tom, že systémy předávající poštu by takové úpravy dělat neměly, není od věci zabezpečit DKIM tak, aby i takto upravená zpráva byla validovatelná. K tomu slouží nastavení kanonizace na relaxed/relaxed (první hodnota se vztahuje k hlavičkám, druhá k tělu zprávy).

Další zajímavou volbou je vynucené podepsání některých hlaviček, byť ve zprávě nejsou přítomny. Vzhledem k tomu, že hlavičky se během cesty zprávy sítí přidávají, nepodepisuje DKIM všechny, ale pouze ty, které byly vyjmenovány ve volbě h= v samotném DKIM podpisu. Typicky bude obsahovat něco jako:

h=Date:From:To:Subject:From; 

Vyskytuje-li se hlavička vícekrát, musí ji i metadata podpisu obsahovat vícekrát; při validaci se přibírají hlavičky postupně odspodu. Hlavičky, které nejsou v seznamu uvedeny, případně další výskyty uvedených hlaviček, nejsou do podpisu zahrnuty. Pokud v nastavení OpenDKIM ponecháme zapnutou volbu OversignHeaders From, bude do podpisu zahrnuta i neexistující druhá hlavička From. Díky tomu případný útočník nemůže do zprávy dodatečně vložit druhou hlavičku From, která by nebyla DKIMem kryta a mohla tak příjemce zprávy uvést v omyl, kdo je skutečným autorem zprávy.

Dlužno ale poznamenat, že RFC 5322 vícenásobné použití hlavičky From zapovídá. Stejný princip je ale možné použít také třeba pro hlavičku Reply-To, čímž bychom útočníkovi zabránili nedetekovatelně odklonit odpovědi na zprávu na jinou adresu.

Ověřování v Thunderbirdu

Protože k ověřování podpisů stačí samotná zpráva, tak jak je uložena v poštovní schránce, je možné podpis ověřovat i přímo v poštovním klientovi. Pro Thunderbird existuje rozšíření DKIM Verifier, které DKIM validuje při načítání zprávy a o výsledku informuje podbarvením adresy odesílatele.

validní podpis, podepsaný autorovou doménou

validní podpis, podepsaný třetí stranou

nevalidní podpis

Politika podepisování ADSP

Samotný DKIM nijak nestanovuje, co se má stát se zprávami, které podepsané nejsou, ani to, jakou váhu má DKIM podpis od jiné domény než té, která je uvedena v adrese odesílatele. Aby mohl DKIM opravdu pomoci proti šíření podvržených zpráv, definuje RFC 5617 politiku Author Domain Signing Practices. Jedná se o další DNS záznam typu TXT na pevně dané subdoméně _adsp._domainkey, který může definovat jednu ze tří politik:

název význam
dkim=unknown politika neexistuje (výchozí)
dkim=all všechny zprávy mají být podepsány autorovou doménou
dkim=discardable zprávy, jejichž validace selže, mají být zahozeny

Nejpřísnější politika discardable je určena výhradně pro domény, které odesílají strojem generované e-maily, u kterých je důležitý boj proti podvrženým zprávám, například automatická sdělení bankovních institucí, případně pro domény, které vůbec nejsou určeny pro e-mail. Tato volba není určena pro domény, ve kterých mají schránky uživatelé. To proto, že existuje několik málo legitimních služeb, případně služeb na hranici, které přísnou podmínku podepsání nesplní, ale je žádoucí, aby byly přesto doručeny. Takovou službou může být posílání e-pohlednic či článků e-mailem, nebo mnohem častěji e-mailové konference.

Problém s e-mailovými konferencemi

Elektronický podpis, který DKIM používá, chrání zprávu a několik jejích hlaviček před modifikací a je tedy nekompatibilní s většinou systémů, které zprávy modifikují. Pokud tedy máte ve své síti antivir, který s oblibou do všech zpráv připíše „Odchozí zpráva neobsahuje viry, zkontrovoláno nejlepším antivirem na světě“, případně vás právní oddělení donutilo ke každému e-mailu připsat „právní doložku“ nutící nahodilé příjemce zprávu okamžitě smazat a všechno, co v ní bylo napsáno, zapomenout, je nutné takové systémy buď nepoužívat, nebo zařadit ještě před vytvoření DKIM podpisu.

Zařízení, která legitimně zprávu modifikují, jsou i e-mailové konference. U nich je žádoucí, aby zpráva přeposlaná od konference převzala identitu toho, kdo ji do konference odeslal. Zároveň ale konference zprávu často modifikuje způsobem, který není slučitelný s DKIM podpisem:

  1. odstraňuje nevhodné přílohy
  2. přidává patičku s informacemi o konferenci
  3. přidává název konference do předmětu zprávy
  4. přidává hlavičku Reply-To na adresu konference

Problém je poměrně obsáhlý a nemá jediné vždy správné řešení. Věnuje se mu celé RFC 6377, publikované též jako BCP 167. Jedním z navržených postupů, jak se má konference chovat k podepsaným zprávám, které modifikuje, je následující:

  1. validovat všechny DKIM podpisy
  2. výsledek validace uložit do hlavičky Authentication-Results při současném odstranení všech předchozích hlaviček tohoto typu
  3. odstranit ze zprávy všechny hlavičky s DKIM podpisy
  4. podepsat zprávu vlastním klíčem, do podepsaných hlaviček přitom zahrnout i hlavičku  Authentication-Results

Konference, která zprávy modifikuje, by také měla odmítat příspěvky a pokusy o přihlášení od adres v doménách, jejichž ADSP politika je stanovena na discardable. Takové zprávy by totiž po modifikaci konferencí byly nejspíše odmítnuty, což by konference mohla nesprávně vyhodnotit jako nefunkční schránku a respondenta odhlásit.

Pozitivní vylepšení s minimem negativních efektů

Standard DKIM byl primárně vyvinut jako vylepšení stávajícího systému s důrazem na možnost postupného nasazení bez rizika poškození funkčnosti stávající e-mailové infrastruktury. Postupně zprávy podepsané DKIMem začaly být zvýhodňovány velkými hráči na poli elektronické pošty, což zvýšilo zájem o technologii, zejména mezi rozesílateli hromadné obchodní korespondence.

zabbix_tip

Politika ADSP, jejímž cílem je použít DKIM k vynucení jistého standardu v doručování zpráv již tak úspěšná není, zejména s ohledem na e-mailové konference, jejíchž velká část doposud není DKIM-friendly. Následky tohoto problému jsou ale mnohem mírnější než v případě problému s přeposíláním pošty u SPF.

Ačkoli je politika ADSP stále používána, začíná být postupně vytlačována standardem DMARC, který z ADSP vychází a přestavuje obecný rámec konzistentního nakládání s výsledky validace jak DKIMu, tak i SPF. Budeme se mu věnovat v příští části seriálu.

Další čtení

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »