#!hs2 ######################################################################## # Script : Scheduler.hsm # Purpose : Scheduler for Hamster # Time-stamp: ######################################################################## # Known Bugs: --- ######################################################################## #!load hamster.hsm #!load Timestamp.hsm #!load DetectProvider.hsm # replace this by a module providing your own Exchange($provider) Function #!load Ralf_Jobs.hsm #!initialize debug( 255, "<<< module 'Scheduler.hsm' >>>" ) HamRequireVersion( "1.3.21.0", True ) varset($fritzweb_started,0) # set this to 1 if you do not want this script to start fritzweb varset($fritzweb_start_again,1) # set this to 0 if you do not want this script to restart fritzweb again&again varset($fritzweb_default_provider,"(none)") # default provider for fritzweb varset($mailpull_semaphore,"Scheduler_Semaphore.ini") #internal: varset($newest_printed_mail,0) # used for printing out mails varset($sched_mailout,HamPath + "Mails\Mail.Out") varset($sched_newsout,HamPath + "Groups\News.Out") varset($last_checked_provider, "none") # use when skipping online test in check_online return(0) # ---------------------------------------------------------------------------------- # sub GetMaxMailPullAge($tilllastsend,*$min_mailpull_age,*$max_mailpull_age) # ---------------------------------------------------------------------------------- sub GetMaxMailPullAge($tilllastsend,$freq,*$min_mailpull_age,*$max_mailpull_age) $min_mailpull_age = 2 # check mail every 2 min. (if online) if ($tilllastsend<15) $max_mailpull_age = 10 # every 10 minutes if a mail was send in the last 15 minutes else if ($tilllastsend<35) $max_mailpull_age = 20 # every 20 minutes if a mail was send in the last 35 minutes else if ($tilllastsend<65) $max_mailpull_age = 30 # every 30 minutes if a mail was send in the last 65 minutes else $max_mailpull_age = 60 # otherwise every 60 minutes $min_mailpull_age = 5 # otherwise wait min. 5 minutes before checking mail again (if online) endif endif endif if ($freq > 0) if ($max_mailpull_age > $freq) $max_mailpull_age = $freq endif if ($min_mailpull_age > $freq) $min_mailpull_age = $freq endif endif debug(2,"GetMaxMailPullAge(",$tilllastsend,",",$freq,") sets: $min_mailpull_age=",$min_mailpull_age," $max_mailpull_age=",$max_mailpull_age) endsub ######################################################################## # Scheduler: this is the scheduler # I start it when starting hamster ######################################################################## # NOTES: see customization block below sub Scheduler() var($weAreOnline,$error,$provider,$sleep) var($stdidle,$longidle,$maxidle,$min_mail_age,$max_mail_age,$min_news_age,$max_news_age) var($min_newspull_age,$max_newspull_age) # var($min_mailpull_age,$max_mailpull_age) var($mailpull_semaphore_age,$mailpull_semaphore_non_check) # var($after_send_max_mailpull_age,$after_send_min_mailpull_age,$after_send_mailpull_period) var($lastOutput,$semaphore_frequency) HamMessage(2) # reset log and counters #debug(3) InitSemaphore() $semaphore_frequency = 0 # disabled # -------------------------------------------------------------------------------- # custimize from here... # $stdidle = 15 # min. seconds between online checks (if we like to do task) $longidle = 20 # max. seconds between online checks (if we won't do task soon) $maxidle = 5*60 # idle seconds if Skipping online check # minimum age of mails, before we send them (to give you a chance to edit them again) $min_mail_age = 2 $max_mail_age = 6 # maximum age of mails in minutes (after this time connect is forced) # Notes: - between these to ages mail is sent only if we are online # - if newest mail is less than $min_mail_age minutes old, no connect will be forced! # - otherwise a connect will be forced if oldest mail is older than max_mail_age $min_news_age = 5 # same as above for news $max_news_age = 15 # $min_mailpull_age = 5 # after this time (in minutes) a mail pull is done if online # $max_mailpull_age = 60 # after this time (in minutes) a mail pull is forced # $after_send_min_mailpull_age = 2 # if a mail has been sent, min_mailpull_age is reduced to that time # $after_send_max_mailpull_age = 10 # if a mail has been sent, max_mailpull_age is reduced to that time # $after_send_mailpull_period = 62 # for that period $min_newspull_age = 20 # after this time (in minutes) a news pull is done if online $max_newspull_age = 8*60 # after this time (in minutes) a news pull is forced # minimum age of semaphore (semaphore is set by DoExchangeXXX) $mailpull_semaphore_age = 150 # after this time (in seconds) # if semaphore was not checked for this time (in minutes) # a mail pull is forced (we assume computer was shut down) $mailpull_semaphore_non_check = 999 # nearly disabled # # ...till here # -------------------------------------------------------------------------------- while (1) $sleep = 0 check_online($weAreOnline,$provider) var($faked) $faked = 0 debug(3,"faked="+$faked+" provider="+$provider+" weAreOnline="+$weAreOnline+" fritzweb_started="+$fritzweb_started) if ($provider=="" && $weAreOnline==0) if ($fritzweb_started && $fritzweb_start_again) $fritzweb_started = 0 endif if ($fritzweb_started==0) # assuming fritzweb is not started yet $provider = $fritzweb_default_provider # so we test fritzweb default provider $weAreOnline = 0 $faked = 1 endif endif debug(3,"faked="+$faked+" provider="+$provider+" weAreOnline="+$weAreOnline+" fritzweb_started="+$fritzweb_started) var($mail_to_send,$news_to_send,$mail_to_receive,$news_to_receive) $mail_to_send = 0 $news_to_send = 0 $mail_to_receive = 0 $news_to_receive = 0 # the values of the above 4 variables have the following meaning: # # -1 -> not allowed (yet) # 0 -> no need # 1 -> if online # 2 -> yess - go online # var($allowed_from,$forced_from) # used for guessing next event $allowed_from = 0 $forced_from = 0 if ($provider!="") if ($weAreOnline==0) debug(2,"We may connect to '"+$provider+"'") else debug(2,"We are connected to '"+$provider+"'") endif # check message times $mail_to_send = check_to_send($sched_mailout,$min_mail_age,$max_mail_age,$sleep,$allowed_from,$forced_from) if ($mail_to_send!=-1) # if mail may be sent (if any) $news_to_send = check_to_send($sched_newsout,$min_news_age,$max_news_age,$sleep,$allowed_from,$forced_from) if ($news_to_send!=-1) # if articles may be sent (if any) var($since_last_mailpull,$since_last_newspull,$force_pull_at,$t,$t2) $t = time var($fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP) $since_last_mailpull = $t $since_last_newspull = $t varset($possibility,1) while (Servers4Provider($provider,$possibility,$fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP)) if ($fetchmail!="" || $sendmail!="") $t2 = GetTimestamp(MailPullEventName($fetchmail)) if ($t2 < $since_last_mailpull) $since_last_mailpull = $t2 endif endif if ($pullnews!="") $t2 = GetTimestamp(NewsPullEventName($pullnews)) if ($t2 < $since_last_newspull) $since_last_newspull = $t2 endif endif $possibility = $possibility+1 endwhile var($lastsent,$curr_max_mailpull_age,$curr_min_mailpull_age) $lastsent = GetTimestamp("LastMailSent") GetMaxMailPullAge(($t - $lastsent)/60, $semaphore_frequency, $curr_min_mailpull_age, $curr_max_mailpull_age) # $period_end = $lastsent + $after_send_mailpull_period*60 # $curr_max_mailpull_age = $max_mailpull_age # $curr_min_mailpull_age = $min_mailpull_age # if ($period_end > $t) # # pull again more early to get answer # $curr_max_mailpull_age = $after_send_max_mailpull_age # $curr_min_mailpull_age = $after_send_min_mailpull_age # endif $force_pull_at = $since_last_mailpull + $curr_max_mailpull_age*60 $forced_from = iif(($force_pull_at<$forced_from) || $forced_from==0,$force_pull_at,$forced_from) debug(3,"[mail] force_pull_at="+ GetReadableTime($force_pull_at)) debug(3,"[mail] forced_from="+GetReadableTime($forced_from)) $force_pull_at = $since_last_newspull + $max_newspull_age*60 $forced_from = iif($force_pull_at<$forced_from || $forced_from==0,$force_pull_at,$forced_from) debug(3,"[news] force_pull_at="+ GetReadableTime($force_pull_at)) debug(3,"[news] forced_from="+GetReadableTime($forced_from)) $since_last_mailpull = $t - $since_last_mailpull $since_last_newspull = $t - $since_last_newspull if ($since_last_mailpull>($curr_min_mailpull_age*60)) $mail_to_receive = 1 if ($since_last_mailpull>($curr_max_mailpull_age*60)) $mail_to_receive = 2 endif endif if ($since_last_newspull>($min_newspull_age*60)) $news_to_receive = 1 if ($since_last_newspull>($max_newspull_age*60)) $news_to_receive = 2 endif endif endif endif varset($ignore_new_stuff,0) varset($force_now,0) # check mailpull semaphore (use by 'exchange now') var($since_last_semaphore_check) $since_last_semaphore_check = MinutesSinceLast("Semaphore_Check") if ($since_last_semaphore_check > $mailpull_semaphore_non_check) $mail_to_receive = 2 else var($sem_set,$sem_age,$sem_provider,$freq) $sem_set = IniRead($mailpull_semaphore,"", "SemaphoreSet",0) if ($sem_set) # semaphore ist set $sem_age = IniRead($mailpull_semaphore,"", "SemaphoreUpdated",0) $sem_age = time-eval($sem_age) $force_now = IniRead($mailpull_semaphore,"", "SemaphoreNow",0) $sem_provider = IniRead($mailpull_semaphore,"", "SemaphoreProvider",0) debug(2,"semaphore code:") if ($force_now=2) $freq = IniRead($mailpull_semaphore,"", "SemaphoreFrequency",0) $semaphore_frequency = $freq debug(2,"$semaphore_frequency=",$semaphore_frequency) # $min_mailpull_age = $freq # $max_mailpull_age = $freq # debug(2,"$min_mailpull_age=",$min_mailpull_age," $max_mailpull_age=",$max_mailpull_age) endif if ($sem_provider==$provider) if ($sem_age>$mailpull_semaphore_age || $force_now==1) # $mail_to_receive = 2 if ($force_now==1) # force exchange now (ignore that messages are too new) if ($mail_to_send==-1) $mail_to_send = 1 endif if ($news_to_send==-1) $news_to_send = 1 endif $ignore_new_stuff = 1 # do not check again for new messages endif else $sleep = ($mailpull_semaphore_age-$sem_age)*1000 $mail_to_receive = -1 debug(3,"mail_to_receive=-1 [1]") endif else $mail_to_receive = -1 # wrong provider -> not now debug(3,"mail_to_receive=-1 [2] sem_provider=",$sem_provider," $provider=",$provider," sem_age=",$sem_age) if ($sem_age>60) # if $sem_provider is wrong after 60 sec -> correct to current $provider IniWrite($mailpull_semaphore,"","SemaphoreProvider",$provider) debug(0,"Provider corrected to ",$provider) endif endif endif endif SetTimestamp("Semaphore_Check",time) #check if exchange should be done NOW: varset($do_exchange,0) if ($mail_to_send!=-1 && $news_to_send!=-1 && $mail_to_receive!=-1) # if not forbidden yet var($fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP) varset($fetchmail_found,0) varset($sendmail_found,0) varset($postnews_found,0) varset($pullnews_found,0) varset($possibility,1) while (Servers4Provider($provider,$possibility,$fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP)) $fetchmail_found = $fetchmail_found || $fetchmail!="" $sendmail_found = $sendmail_found || $sendmail!="" $postnews_found = $postnews_found || $postnews!="" $pullnews_found = $pullnews_found || $pullnews!="" $possibility = $possibility+1 endwhile # correct flags if provider does not support service if ($fetchmail_found==0) $mail_to_receive = 0 endif if ($sendmail_found==0) if ($mail_to_send) #provider can't send mail MsgBox("'"+$provider+"' not suitable to send mail!","Scheduler",0x10|0x0) endif $mail_to_send = 0 endif if ($pullnews_found==0) $news_to_receive = 0 endif if ($postnews_found==0) $news_to_send = 0 endif if ($mail_to_send==2 || $mail_to_receive==2 || _ $news_to_send==2 || $news_to_receive==2) # if max time reached for send/receive $do_exchange = 1 # then force exchange else if ($mail_to_send==1 || $mail_to_receive==1 || _ $news_to_send==1 || $news_to_receive==1) # if we have sth to exchange $do_exchange = $weAreOnline # then exchange if we are online endif endif if ($do_exchange) # if exchange should be done NOW.. debug(3,"faked="+$faked) if ($faked) StartAndWaitForFritzweb("mail",$fritzweb_started,$weAreOnline,$provider) else if ($fritzweb_started==0 && FritzWebIsRunning()) $fritzweb_started = 1 endif endif # if sending mail -> mark semaphore if ($mail_to_send>0) SetTimestamp("LastMailSent",time) endif # stop NNTP- and SMTP-Server and test msg-times again # to avoid the case that you've sent a message to hamster just before HamMessage ( 3, 0 ) # Stop NNTP-Server HamMessage ( 5, 0 ) # Stop SMTP-Server varset($new_stuff,0) if ($ignore_new_stuff!=1) #if we do not ignore new messages var($snd,$slp,$d1,$d2) $snd = check_to_send($sched_mailout,$min_mail_age,$max_mail_age,$slp,$d1,$d2) if ($snd==-1) $new_stuff = 1 # oops new mail arrived before stopping SMTP-Server endif $snd = check_to_send($sched_newsout,$min_news_age,$max_news_age,$slp,$d1,$d2) if ($snd==-1) $new_stuff = 1 # same for news endif endif if ($new_stuff==0) # if no new messages # now we really do the exchange DoExchangeNow_internal($provider,$force_now) ResetSemaphore() endif HamMessage ( 5, 1 ) # Start SMTP-Server HamMessage ( 3, 1 ) # Start NNTP-Server endif endif debug(3,"do_exchange="+ $do_exchange+" faked="+ $faked+" sleep="+ $sleep) if ($do_exchange==0 && $faked==1 && $sleep==0) # fritzweb not active yet => sleep longer $sleep = $longidle*1000 endif else #no valid provider if ($weAreOnline) #we are connected to a provider we cannot exchange sth with $sleep = $longidle*1000 endif endif # calculate how long we sleep # do not check more often than stdidle: # do not wait longer than longidle or maxidle if (IniRead($mailpull_semaphore,"","SkipOnlineCheck",0) = 1) $sleep = ($forced_from - time) * 1000 if ($sleep>($maxidle*1000)) $sleep = $maxidle*1000 endif else if ($sleep<($stdidle*1000)) $sleep = $stdidle*1000 endif if ($sleep>($longidle*1000)) $sleep = $longidle*1000 endif endif # guess time of next exchange (very primitive guess) if ($allowed_from>0 || $forced_from>0) var($Output,$eventtime) $eventtime = GetReadableTime(iif($allowed_from > $forced_from,$allowed_from,$forced_from)) debug(3,"[next event] allowed_from="+GetReadableTime($allowed_from)) debug(3,"[next event] forced_from="+GetReadableTime($forced_from)) $Output = "next event: "+$eventtime+" / next check: "+($sleep/1000)+"sec " +_ "(ms: "+ $mail_to_send+" mr: "+ $mail_to_receive+ _ " ns:"+ $news_to_send+" nr:"+ $news_to_receive+")" if ($Output!=$lastOutput) # if guess differs from last guess then show it print($Output) $lastOutput = $Output endif endif debug(3,"---------------- Sleeping "+$sleep) sleep($sleep) #be idle endwhile endsub ######################################################################## # SetSemaphore: Set the semaphore which is tested by Scheduler() ######################################################################## # [IN] $provider: name of provider we should try to connect to # $now==1 -> ignore timestamps (mail/article to new to send) # ==2 -> check mail every $freq min # $freq: frequency for mail checking # [OUT] --- ######################################################################## # NOTE: 1. I use this when I receive an email-announce from Mailjack # (see >>>http://www.mailjack.de/<<<) # Mailjack starts script; script calls SetSemaphore("provider",0) # 2. I also use it to force mail-exchange now (see DoExchangeNow) # 3. Used to set mail check frequency to sub SetSemaphore($provider,$now,$freq) debug(0,"SetSemaphore("+$provider+")") if ($now != 2) $freq = 0 endif IniWrite($mailpull_semaphore,"","SemaphoreSet", 1) IniWrite($mailpull_semaphore,"","SemaphoreUpdated",time+$freq*60) IniWrite($mailpull_semaphore,"","SemaphoreProvider",$provider) IniWrite($mailpull_semaphore,"","SemaphoreNow",$now) IniWrite($mailpull_semaphore,"","SemaphoreFrequency",$freq) endsub sub InitSemaphore() IniWrite($mailpull_semaphore,"","SemaphoreSet",0) IniWrite($mailpull_semaphore,"","SemaphoreUpdated",0) IniWrite($mailpull_semaphore,"","SemaphoreProvider","") IniWrite($mailpull_semaphore,"","SemaphoreNow",0) IniWrite($mailpull_semaphore,"","SemaphoreFrequency",0) EnableOnlineCheck() endsub sub ResetSemaphore() IniWrite($mailpull_semaphore,"","SemaphoreSet",0) IniWrite($mailpull_semaphore,"","SemaphoreUpdated",0) IniWrite($mailpull_semaphore,"","SemaphoreProvider","") varset($now,IniRead($mailpull_semaphore,"", "SemaphoreNow",0)) if ($now!=2) IniWrite($mailpull_semaphore,"","SemaphoreNow",0) endif endsub sub StartAndWaitForFritzweb($mode,*$fritzweb_started,*$weAreOnline,*$provider) StartFritzWeb($mode) $provider = "" varset($count,0) while (1) # wait for fritzweb to startup sleep(3000) check_online($weAreOnline,$provider) if ($provider!="") $fritzweb_started = 1 break endif inc($count) if (($count=3) || ($count=6) || ($count=9)) debug(1,"Problem starting Fritzweb.. (deactivated or unknown provider?)") endif if ($count>=10) # after 30 seconds.. # if this occurs then check Provider4Connection in DetectProvider.hsm MsgBox("Problem starting Fritzweb.. (deactivated or unknown provider?)","Hamster Error",0x10|0x0) $count=0 endif endwhile endsub ######################################################################## # DisableOnlineCheck: disable time-consuming online check ######################################################################## # NOTE: after calling that Scheduler will not check if online sub DisableOnlineCheck() IniWrite($mailpull_semaphore,"","SkipOnlineCheck",1) endsub sub EnableOnlineCheck() IniWrite($mailpull_semaphore,"","SkipOnlineCheck",0) endsub ######################################################################## # DoExchangeNow: used to force Mail/News-Exchange from another script ######################################################################## # NOTE: none sub DoExchangeNow() var($weAreOnline,$provider,$dummy) StartAndWaitForFritzweb("quickmail",$dummy,$weAreOnline,$provider) if ($provider!="") SetSemaphore($provider,1,0) else MsgBox("No provider - check Fritz!Web!","DoExchangeNow",0x10|0x0) endif endsub # same as above, but uses $mailpull_semaphore_age (see Scheduler()) sub DoExchangeSoon() var($weAreOnline,$provider,$dummy) StartAndWaitForFritzweb("quickmail",$dummy,$weAreOnline,$provider) if ($provider!="") SetSemaphore($provider,0,0) else MsgBox("No provider - check Fritz!Web!","DoExchangeSoon",0x10|0x0) endif endsub # sub DoExchangeEveryMin($freq) var($weAreOnline,$provider,$dummy) StartAndWaitForFritzweb("quickmail",$dummy,$weAreOnline,$provider) if ($provider!="") SetSemaphore($provider,2,$freq) else MsgBox("No provider - check Fritz!Web!","DoExchangeEveryMin",0x10|0x0) endif endsub ######################################################################## # DoExchangeNow_internal: do the Mail/News-Exchange ######################################################################## # [IN] $provider: name of provider we are connected/may connect to # [OUT] --- ######################################################################## # NOTE: you have to provide a function 'Exchange($provider)' which # does your Mail/News-Exchange with $provider sub DoExchangeNow_internal($provider,$instantly) #make logfile entries of outbound varset($new_mail,0) varset($new_news,0) print("-------------------- Current Outbound: ") LogOutbound($sched_mailout,"*.msg",$newest_printed_mail,$new_mail) LogOutbound($sched_newsout,"*.msg",$newest_printed_mail,$new_news) $newest_printed_mail = iif ($newest_printed_mail < $new_mail,$new_mail,$newest_printed_mail) $newest_printed_mail = iif ($newest_printed_mail < $new_news,$new_news,$newest_printed_mail) #do real exchange debug(1,"Doing exchange with '"+$provider+"' now!") #MsgBox("Doing exchange with "+$provider) var($success) $success = Exchange($provider,$instantly) # use your own function here # warning("Fake exchange") # $success = 1 if ($success==0) debug(1,"Exchange failed!") else debug(1,"Exchange finished!") #mark as done var($fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP) varset($possibility,1) while (Servers4Provider($provider,$possibility,$fetchmail,$sendmail,$postnews,$pullnews,$POPvorSMTP)) if ($fetchmail!="" || $sendmail!="") SetTimestamp(MailPullEventName($fetchmail),time) endif if ($pullnews!="") SetTimestamp(NewsPullEventName($pullnews),time) endif $possibility = $possibility+1 endwhile endif endsub ######################################################################## # check_to_send: check if there are mails/news to send ######################################################################## # [IN] $dir : dir to check for *.msg files # $wanted_min_age : minimum age of newest *.msg file (more important criteria) # $wanted_max_age : maximum age of oldest *.msg file (less important criteria) # [OUT] (return) : -1 => exchange is prohibited # 0 => exchange is unnecessary # 1 => exchange is ok # 2 => exchange is recommended # $sleep : in case of return(-1) this is set according to # earliest send time # $allowed_from : this is set to the time we are allowed to exchange # $forced_from : this is set to the time we will force exchange ######################################################################## # NOTE: none sub check_to_send($dir,$wanted_min_age,$wanted_max_age,*$sleep,*$allowed_from,*$forced_from) var($min_age,$max_age,$recently) $wanted_min_age = $wanted_min_age*60 #minutes -> seconds $wanted_max_age = $wanted_max_age*60 GetFileAges($dir,"*.msg",$max_age,$min_age) if ($min_age==0) debug(2,"No *.msg in "+$dir) $allowed_from = iif($allowed_from$newest) $newest=$age endif inc($i) endwhile ListFree($files) endsub ######################################################################## # LogOutbound: Log mails/news in outbound into hamster logfile ######################################################################## # [IN] $dir : Path of directory to check (i.e HamPath+"Mails\Mail.out") # $mask : Filemask of files to check (i.e. '*.msg') # [OUT] $from : don't check files older than this # $newest : Time of newest matching file ######################################################################## # NOTE: if no file matches $newest is 0 sub LogOutbound($dir,$mask,$from,*$newest) var($files,$i,$count,$age) $newest = 0 $files = ListAlloc ListFiles($files,$dir+"\"+$mask) # "\" $count = ListCount($files) $i = 0 while ($i < $count) #print($i,": ",ListGet($files,$i)) varset($fname,ListGet($files,$i)) varset($lfname,$dir+"\"+$fname) # "\" $age = FileTime($lfname) if ($age > $from) var($msg,$j,$count2,$pcount) ListAlloc($msg) if (ListLoad($msg,$lfname)!=0) MsgBox("Can't load "+$lfname,"Hamster Error",0x10|0x0) endif debug(2,"checking msg: "+$lfname) $count2 = ListCount($msg) $pcount = 0 $j = 0 while ($j < $count2 && $pcount<3) var($line) $line = ListGet($msg,$j) debug(2,"checking line: "+$line) if (copy($line,1,4)=="To: " || copy($line,1,12)=="Newsgroups: " || _ copy($line,1,6)=="Date: " || copy($line,1,9)=="Subject: ") print($fname,": ",$line) inc($pcount) endif if (copy($line,1,6)=="From: ") if (RE_Match($line,"admin\@hamster\.invalid")) print("Renaming "+$fname+" (invalid sender)") if (FileRename($dir+"\"+$fname,$dir+"\"+$fname+".invalid")!=0) MsgBox("Error renaming "+$fname,"Hamster Error",0x10|0x0) endif $age = 0 endif endif inc($j) endwhile ListFree($msg) endif if ($newest=0 || $age>$newest) $newest=$age endif inc($i) endwhile ListFree($files) endsub