app_minivm.c 115 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 1999 - 2005, Digium, Inc.
  5. * and Edvina AB, Sollentuna, Sweden
  6. *
  7. * Mark Spencer <markster@digium.com> (Comedian Mail)
  8. * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
  9. *
  10. * See http://www.asterisk.org for more information about
  11. * the Asterisk project. Please do not directly contact
  12. * any of the maintainers of this project for assistance;
  13. * the project provides a web site, mailing lists and IRC
  14. * channels for your use.
  15. *
  16. * This program is free software, distributed under the terms of
  17. * the GNU General Public License Version 2. See the LICENSE file
  18. * at the top of the source tree.
  19. */
  20. /*! \file
  21. *
  22. * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
  23. *
  24. * A voicemail system in small building blocks, working together
  25. * based on the Comedian Mail voicemail system (app_voicemail.c).
  26. *
  27. * \par See also
  28. * \arg \ref Config_minivm_examples
  29. * \arg \ref App_minivm
  30. *
  31. * \ingroup applications
  32. *
  33. * \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system
  34. *
  35. * This is a minimal voicemail system, building blocks for something
  36. * else. It is built for multi-language systems.
  37. * The current version is focused on accounts where voicemail is
  38. * forwarded to users in e-mail. It's work in progress, with loosed ends hanging
  39. * around from the old voicemail system and it's configuration.
  40. *
  41. * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
  42. * in the future.
  43. *
  44. * Dialplan applications
  45. * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
  46. * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
  47. * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
  48. * - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
  49. * - minivmAccMess - Record personal messages (busy | unavailable | temporary)
  50. *
  51. * Dialplan functions
  52. * - MINIVMACCOUNT() - A dialplan function
  53. * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
  54. *
  55. * CLI Commands
  56. * - minivm list accounts
  57. * - minivm list zones
  58. * - minivm list templates
  59. * - minivm show stats
  60. * - minivm show settings
  61. *
  62. * Some notes
  63. * - General configuration in minivm.conf
  64. * - Users in realtime or configuration file
  65. * - Or configured on the command line with just the e-mail address
  66. *
  67. * Voicemail accounts are identified by userid and domain
  68. *
  69. * Language codes are like setlocale - langcode_countrycode
  70. * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
  71. * language_country like setlocale().
  72. *
  73. * Examples:
  74. * - Swedish, Sweden sv_se
  75. * - Swedish, Finland sv_fi
  76. * - English, USA en_us
  77. * - English, GB en_gb
  78. *
  79. * \par See also
  80. * \arg \ref Config_minivm
  81. * \arg \ref Config_minivm_examples
  82. * \arg \ref Minivm_directories
  83. * \arg \ref app_minivm.c
  84. * \arg Comedian mail: app_voicemail.c
  85. * \arg \ref descrip_minivm_accmess
  86. * \arg \ref descrip_minivm_greet
  87. * \arg \ref descrip_minivm_record
  88. * \arg \ref descrip_minivm_delete
  89. * \arg \ref descrip_minivm_notify
  90. *
  91. * \arg \ref App_minivm_todo
  92. */
  93. /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
  94. *
  95. * The directory structure for storing voicemail
  96. * - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
  97. * - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
  98. * - Domain MVM_SPOOL_DIR/domain
  99. * - Username MVM_SPOOL_DIR/domain/username
  100. * - /greet : Recording of account owner's name
  101. * - /busy : Busy message
  102. * - /unavailable : Unavailable message
  103. * - /temp : Temporary message
  104. *
  105. * For account anita@localdomain.xx the account directory would as a default be
  106. * \b /var/spool/asterisk/voicemail/localdomain.xx/anita
  107. *
  108. * To avoid transcoding, these sound files should be converted into several formats
  109. * They are recorded in the format closest to the incoming streams
  110. *
  111. *
  112. * Back: \ref App_minivm
  113. */
  114. /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
  115. * \section Example dialplan scripts for Mini-Voicemail
  116. * \verbinclude extensions_minivm.conf.sample
  117. *
  118. * Back: \ref App_minivm
  119. */
  120. /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
  121. * - configure accounts from AMI?
  122. * - test, test, test, test
  123. * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
  124. * "The extension you are calling"
  125. * - For trunk, consider using channel storage for information passing between small applications
  126. * - Set default directory for voicemail
  127. * - New app for creating directory for account if it does not exist
  128. * - Re-insert code for IMAP storage at some point
  129. * - Jabber integration for notifications
  130. * - Figure out how to handle video in voicemail
  131. * - Integration with the HTTP server
  132. * - New app for moving messages between mailboxes, and optionally mark it as "new"
  133. *
  134. * For Asterisk 1.4/trunk
  135. * - Use string fields for minivm_account
  136. *
  137. * Back: \ref App_minivm
  138. */
  139. /*** MODULEINFO
  140. <support_level>extended</support_level>
  141. ***/
  142. #include "asterisk.h"
  143. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  144. #include <ctype.h>
  145. #include <sys/time.h>
  146. #include <sys/stat.h>
  147. #include <sys/mman.h>
  148. #include <time.h>
  149. #include <dirent.h>
  150. #include <locale.h>
  151. #include "asterisk/paths.h" /* use various paths */
  152. #include "asterisk/lock.h"
  153. #include "asterisk/file.h"
  154. #include "asterisk/channel.h"
  155. #include "asterisk/pbx.h"
  156. #include "asterisk/config.h"
  157. #include "asterisk/say.h"
  158. #include "asterisk/module.h"
  159. #include "asterisk/app.h"
  160. #include "asterisk/dsp.h"
  161. #include "asterisk/localtime.h"
  162. #include "asterisk/cli.h"
  163. #include "asterisk/utils.h"
  164. #include "asterisk/linkedlists.h"
  165. #include "asterisk/callerid.h"
  166. #include "asterisk/stasis.h"
  167. #include "asterisk/stasis_channels.h"
  168. #include "asterisk/json.h"
  169. /*** DOCUMENTATION
  170. <application name="MinivmRecord" language="en_US">
  171. <synopsis>
  172. Receive Mini-Voicemail and forward via e-mail.
  173. </synopsis>
  174. <syntax>
  175. <parameter name="mailbox" required="true" argsep="@">
  176. <argument name="username" required="true">
  177. <para>Voicemail username</para>
  178. </argument>
  179. <argument name="domain" required="true">
  180. <para>Voicemail domain</para>
  181. </argument>
  182. </parameter>
  183. <parameter name="options" required="false">
  184. <optionlist>
  185. <option name="0">
  186. <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
  187. </option>
  188. <option name="*">
  189. <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
  190. </option>
  191. <option name="g">
  192. <argument name="gain">
  193. <para>Amount of gain to use</para>
  194. </argument>
  195. <para>Use the specified amount of gain when recording the voicemail message.
  196. The units are whole-number decibels (dB).</para>
  197. </option>
  198. </optionlist>
  199. </parameter>
  200. </syntax>
  201. <description>
  202. <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
  203. <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
  204. <para>If there's no user account for that address, a temporary account will be used with default options.</para>
  205. <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
  206. of the message will be stored in <variable>MVM_DURATION</variable></para>
  207. <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
  208. execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
  209. are received and the requested extension exist in the current context.</para></note>
  210. <variablelist>
  211. <variable name="MVM_RECORD_STATUS">
  212. <para>This is the status of the record operation</para>
  213. <value name="SUCCESS" />
  214. <value name="USEREXIT" />
  215. <value name="FAILED" />
  216. </variable>
  217. </variablelist>
  218. </description>
  219. </application>
  220. <application name="MinivmGreet" language="en_US">
  221. <synopsis>
  222. Play Mini-Voicemail prompts.
  223. </synopsis>
  224. <syntax>
  225. <parameter name="mailbox" required="true" argsep="@">
  226. <argument name="username" required="true">
  227. <para>Voicemail username</para>
  228. </argument>
  229. <argument name="domain" required="true">
  230. <para>Voicemail domain</para>
  231. </argument>
  232. </parameter>
  233. <parameter name="options" required="false">
  234. <optionlist>
  235. <option name="b">
  236. <para>Play the <literal>busy</literal> greeting to the calling party.</para>
  237. </option>
  238. <option name="s">
  239. <para>Skip the playback of instructions for leaving a message to the calling party.</para>
  240. </option>
  241. <option name="u">
  242. <para>Play the <literal>unavailable</literal> greeting.</para>
  243. </option>
  244. </optionlist>
  245. </parameter>
  246. </syntax>
  247. <description>
  248. <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
  249. <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
  250. <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
  251. message exists for the account.</para>
  252. <variablelist>
  253. <variable name="MVM_GREET_STATUS">
  254. <para>This is the status of the greeting playback.</para>
  255. <value name="SUCCESS" />
  256. <value name="USEREXIT" />
  257. <value name="FAILED" />
  258. </variable>
  259. </variablelist>
  260. </description>
  261. </application>
  262. <application name="MinivmNotify" language="en_US">
  263. <synopsis>
  264. Notify voicemail owner about new messages.
  265. </synopsis>
  266. <syntax>
  267. <parameter name="mailbox" required="true" argsep="@">
  268. <argument name="username" required="true">
  269. <para>Voicemail username</para>
  270. </argument>
  271. <argument name="domain" required="true">
  272. <para>Voicemail domain</para>
  273. </argument>
  274. </parameter>
  275. <parameter name="options" required="false">
  276. <optionlist>
  277. <option name="template">
  278. <para>E-mail template to use for voicemail notification</para>
  279. </option>
  280. </optionlist>
  281. </parameter>
  282. </syntax>
  283. <description>
  284. <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
  285. <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
  286. account for that address, a temporary account will be used with default options (set in
  287. <filename>minivm.conf</filename>).</para>
  288. <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
  289. file name and available in the template for the message.</para>
  290. <para>If no template is given, the default email template will be used to send email and default pager
  291. template to send paging message (if the user account is configured with a paging address.</para>
  292. <variablelist>
  293. <variable name="MVM_NOTIFY_STATUS">
  294. <para>This is the status of the notification attempt</para>
  295. <value name="SUCCESS" />
  296. <value name="FAILED" />
  297. </variable>
  298. </variablelist>
  299. </description>
  300. </application>
  301. <application name="MinivmDelete" language="en_US">
  302. <synopsis>
  303. Delete Mini-Voicemail voicemail messages.
  304. </synopsis>
  305. <syntax>
  306. <parameter name="filename" required="true">
  307. <para>File to delete</para>
  308. </parameter>
  309. </syntax>
  310. <description>
  311. <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
  312. <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
  313. <variablelist>
  314. <variable name="MVM_DELETE_STATUS">
  315. <para>This is the status of the delete operation.</para>
  316. <value name="SUCCESS" />
  317. <value name="FAILED" />
  318. </variable>
  319. </variablelist>
  320. </description>
  321. </application>
  322. <application name="MinivmAccMess" language="en_US">
  323. <synopsis>
  324. Record account specific messages.
  325. </synopsis>
  326. <syntax>
  327. <parameter name="mailbox" required="true" argsep="@">
  328. <argument name="username" required="true">
  329. <para>Voicemail username</para>
  330. </argument>
  331. <argument name="domain" required="true">
  332. <para>Voicemail domain</para>
  333. </argument>
  334. </parameter>
  335. <parameter name="options" required="false">
  336. <optionlist>
  337. <option name="u">
  338. <para>Record the <literal>unavailable</literal> greeting.</para>
  339. </option>
  340. <option name="b">
  341. <para>Record the <literal>busy</literal> greeting.</para>
  342. </option>
  343. <option name="t">
  344. <para>Record the temporary greeting.</para>
  345. </option>
  346. <option name="n">
  347. <para>Account name.</para>
  348. </option>
  349. </optionlist>
  350. </parameter>
  351. </syntax>
  352. <description>
  353. <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
  354. <para>Use this application to record account specific audio/video messages for busy, unavailable
  355. and temporary messages.</para>
  356. <para>Account specific directories will be created if they do not exist.</para>
  357. <variablelist>
  358. <variable name="MVM_ACCMESS_STATUS">
  359. <para>This is the result of the attempt to record the specified greeting.</para>
  360. <para><literal>FAILED</literal> is set if the file can't be created.</para>
  361. <value name="SUCCESS" />
  362. <value name="FAILED" />
  363. </variable>
  364. </variablelist>
  365. </description>
  366. </application>
  367. <application name="MinivmMWI" language="en_US">
  368. <synopsis>
  369. Send Message Waiting Notification to subscriber(s) of mailbox.
  370. </synopsis>
  371. <syntax>
  372. <parameter name="mailbox" required="true" argsep="@">
  373. <argument name="username" required="true">
  374. <para>Voicemail username</para>
  375. </argument>
  376. <argument name="domain" required="true">
  377. <para>Voicemail domain</para>
  378. </argument>
  379. </parameter>
  380. <parameter name="urgent" required="true">
  381. <para>Number of urgent messages in mailbox.</para>
  382. </parameter>
  383. <parameter name="new" required="true">
  384. <para>Number of new messages in mailbox.</para>
  385. </parameter>
  386. <parameter name="old" required="true">
  387. <para>Number of old messages in mailbox.</para>
  388. </parameter>
  389. </syntax>
  390. <description>
  391. <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
  392. <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
  393. subscribed to the mailbox passed in the first parameter.</para>
  394. </description>
  395. </application>
  396. <function name="MINIVMCOUNTER" language="en_US">
  397. <synopsis>
  398. Reads or sets counters for MiniVoicemail message.
  399. </synopsis>
  400. <syntax argsep=":">
  401. <parameter name="account" required="true">
  402. <para>If account is given and it exists, the counter is specific for the account.</para>
  403. <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
  404. </parameter>
  405. <parameter name="name" required="true">
  406. <para>The name of the counter is a string, up to 10 characters.</para>
  407. </parameter>
  408. <parameter name="operand">
  409. <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
  410. <enumlist>
  411. <enum name="i"><para>Increment by value.</para></enum>
  412. <enum name="d"><para>Decrement by value.</para></enum>
  413. <enum name="s"><para>Set to value.</para></enum>
  414. </enumlist>
  415. </parameter>
  416. </syntax>
  417. <description>
  418. <para>The operation is atomic and the counter is locked while changing the value. The counters are stored as text files in the minivm account directories. It might be better to use realtime functions if you are using a database to operate your Asterisk.</para>
  419. </description>
  420. <see-also>
  421. <ref type="application">MinivmRecord</ref>
  422. <ref type="application">MinivmGreet</ref>
  423. <ref type="application">MinivmNotify</ref>
  424. <ref type="application">MinivmDelete</ref>
  425. <ref type="application">MinivmAccMess</ref>
  426. <ref type="application">MinivmMWI</ref>
  427. <ref type="function">MINIVMACCOUNT</ref>
  428. </see-also>
  429. </function>
  430. <function name="MINIVMACCOUNT" language="en_US">
  431. <synopsis>
  432. Gets MiniVoicemail account information.
  433. </synopsis>
  434. <syntax argsep=":">
  435. <parameter name="account" required="true" />
  436. <parameter name="item" required="true">
  437. <para>Valid items are:</para>
  438. <enumlist>
  439. <enum name="path">
  440. <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
  441. </enum>
  442. <enum name="hasaccount">
  443. <para>1 is static Minivm account exists, 0 otherwise.</para>
  444. </enum>
  445. <enum name="fullname">
  446. <para>Full name of account owner.</para>
  447. </enum>
  448. <enum name="email">
  449. <para>Email address used for account.</para>
  450. </enum>
  451. <enum name="etemplate">
  452. <para>Email template for account (default template if none is configured).</para>
  453. </enum>
  454. <enum name="ptemplate">
  455. <para>Pager template for account (default template if none is configured).</para>
  456. </enum>
  457. <enum name="accountcode">
  458. <para>Account code for the voicemail account.</para>
  459. </enum>
  460. <enum name="pincode">
  461. <para>Pin code for voicemail account.</para>
  462. </enum>
  463. <enum name="timezone">
  464. <para>Time zone for voicemail account.</para>
  465. </enum>
  466. <enum name="language">
  467. <para>Language for voicemail account.</para>
  468. </enum>
  469. <enum name="&lt;channel variable name&gt;">
  470. <para>Channel variable value (set in configuration for account).</para>
  471. </enum>
  472. </enumlist>
  473. </parameter>
  474. </syntax>
  475. <description>
  476. <para />
  477. </description>
  478. <see-also>
  479. <ref type="application">MinivmRecord</ref>
  480. <ref type="application">MinivmGreet</ref>
  481. <ref type="application">MinivmNotify</ref>
  482. <ref type="application">MinivmDelete</ref>
  483. <ref type="application">MinivmAccMess</ref>
  484. <ref type="application">MinivmMWI</ref>
  485. <ref type="function">MINIVMCOUNTER</ref>
  486. </see-also>
  487. </function>
  488. <managerEvent language="en_US" name="MiniVoiceMail">
  489. <managerEventInstance class="EVENT_FLAG_CALL">
  490. <synopsis>Raised when a notification is sent out by a MiniVoiceMail application</synopsis>
  491. <syntax>
  492. <channel_snapshot/>
  493. <parameter name="Action">
  494. <para>What action was taken. Currently, this will always be <literal>SentNotification</literal></para>
  495. </parameter>
  496. <parameter name="Mailbox">
  497. <para>The mailbox that the notification was about, specified as <literal>mailbox</literal>@<literal>context</literal></para>
  498. </parameter>
  499. <parameter name="Counter">
  500. <para>A message counter derived from the <literal>MVM_COUNTER</literal> channel variable.</para>
  501. </parameter>
  502. </syntax>
  503. </managerEventInstance>
  504. </managerEvent>
  505. ***/
  506. #ifndef TRUE
  507. #define TRUE 1
  508. #endif
  509. #ifndef FALSE
  510. #define FALSE 0
  511. #endif
  512. #define MVM_REVIEW (1 << 0) /*!< Review message */
  513. #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
  514. #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
  515. #define MVM_SVMAIL (1 << 3)
  516. #define MVM_ENVELOPE (1 << 4)
  517. #define MVM_PBXSKIP (1 << 9)
  518. #define MVM_ALLOCED (1 << 13)
  519. /*! \brief Default mail command to mail voicemail. Change it with the
  520. mailcmd= command in voicemail.conf */
  521. #define SENDMAIL "/usr/sbin/sendmail -t"
  522. #define SOUND_INTRO "vm-intro"
  523. #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
  524. #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
  525. #define EOL "\r\n"
  526. #define MAX_DATETIME_FORMAT 512
  527. #define MAX_NUM_CID_CONTEXTS 10
  528. #define ERROR_LOCK_PATH -100
  529. #define VOICEMAIL_DIR_MODE 0700
  530. #define VOICEMAIL_CONFIG "minivm.conf"
  531. #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
  532. /*! \brief Message types for notification */
  533. enum mvm_messagetype {
  534. MVM_MESSAGE_EMAIL,
  535. MVM_MESSAGE_PAGE
  536. /* For trunk: MVM_MESSAGE_JABBER, */
  537. };
  538. static char MVM_SPOOL_DIR[PATH_MAX];
  539. /* Module declarations */
  540. static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
  541. static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
  542. static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
  543. static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
  544. static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
  545. static char *app_minivm_mwi = "MinivmMWI";
  546. enum minivm_option_flags {
  547. OPT_SILENT = (1 << 0),
  548. OPT_BUSY_GREETING = (1 << 1),
  549. OPT_UNAVAIL_GREETING = (1 << 2),
  550. OPT_TEMP_GREETING = (1 << 3),
  551. OPT_NAME_GREETING = (1 << 4),
  552. OPT_RECORDGAIN = (1 << 5),
  553. };
  554. enum minivm_option_args {
  555. OPT_ARG_RECORDGAIN = 0,
  556. OPT_ARG_ARRAY_SIZE = 1,
  557. };
  558. AST_APP_OPTIONS(minivm_app_options, {
  559. AST_APP_OPTION('s', OPT_SILENT),
  560. AST_APP_OPTION('b', OPT_BUSY_GREETING),
  561. AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
  562. AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
  563. });
  564. AST_APP_OPTIONS(minivm_accmess_options, {
  565. AST_APP_OPTION('b', OPT_BUSY_GREETING),
  566. AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
  567. AST_APP_OPTION('t', OPT_TEMP_GREETING),
  568. AST_APP_OPTION('n', OPT_NAME_GREETING),
  569. });
  570. /*!\internal
  571. * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
  572. struct minivm_account {
  573. char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
  574. char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
  575. char pincode[10]; /*!< Secret pin code, numbers only */
  576. char fullname[120]; /*!< Full name, for directory app */
  577. char email[80]; /*!< E-mail address - override */
  578. char pager[80]; /*!< E-mail address to pager (no attachment) */
  579. char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
  580. char serveremail[80]; /*!< From: Mail address */
  581. char externnotify[160]; /*!< Configurable notification command */
  582. char language[MAX_LANGUAGE]; /*!< Config: Language setting */
  583. char zonetag[80]; /*!< Time zone */
  584. char uniqueid[20]; /*!< Unique integer identifier */
  585. char exit[80]; /*!< Options for exiting from voicemail() */
  586. char attachfmt[80]; /*!< Format for voicemail audio file attachment */
  587. char etemplate[80]; /*!< Pager template */
  588. char ptemplate[80]; /*!< Voicemail format */
  589. unsigned int flags; /*!< MVM_ flags */
  590. struct ast_variable *chanvars; /*!< Variables for e-mail template */
  591. double volgain; /*!< Volume gain for voicemails sent via e-mail */
  592. AST_LIST_ENTRY(minivm_account) list;
  593. };
  594. /*!\internal
  595. * \brief The list of e-mail accounts */
  596. static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
  597. /*!\internal
  598. * \brief Linked list of e-mail templates in various languages
  599. * These are used as templates for e-mails, pager messages and jabber messages
  600. * \ref message_templates
  601. */
  602. struct minivm_template {
  603. char name[80]; /*!< Template name */
  604. char *body; /*!< Body of this template */
  605. char fromaddress[100]; /*!< Who's sending the e-mail? */
  606. char serveremail[80]; /*!< From: Mail address */
  607. char subject[100]; /*!< Subject line */
  608. char charset[32]; /*!< Default character set for this template */
  609. char locale[20]; /*!< Locale for setlocale() */
  610. char dateformat[80]; /*!< Date format to use in this attachment */
  611. int attachment; /*!< Attachment of media yes/no - no for pager messages */
  612. AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
  613. };
  614. /*! \brief The list of e-mail templates */
  615. static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
  616. /*! \brief Options for leaving voicemail with the voicemail() application */
  617. struct leave_vm_options {
  618. unsigned int flags;
  619. signed char record_gain;
  620. };
  621. /*! \brief Structure for base64 encoding */
  622. struct b64_baseio {
  623. int iocp;
  624. int iolen;
  625. int linelength;
  626. int ateof;
  627. unsigned char iobuf[B64_BASEMAXINLINE];
  628. };
  629. /*! \brief Voicemail time zones */
  630. struct minivm_zone {
  631. char name[80]; /*!< Name of this time zone */
  632. char timezone[80]; /*!< Timezone definition */
  633. char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
  634. AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
  635. };
  636. /*! \brief The list of e-mail time zones */
  637. static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
  638. /*! \brief Structure for gathering statistics */
  639. struct minivm_stats {
  640. int voicemailaccounts; /*!< Number of static accounts */
  641. int timezones; /*!< Number of time zones */
  642. int templates; /*!< Number of templates */
  643. struct timeval reset; /*!< Time for last reset */
  644. int receivedmessages; /*!< Number of received messages since reset */
  645. struct timeval lastreceived; /*!< Time for last voicemail sent */
  646. };
  647. /*! \brief Statistics for voicemail */
  648. static struct minivm_stats global_stats;
  649. AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
  650. AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
  651. static FILE *minivmlogfile; /*!< The minivm log file */
  652. static int global_vmminmessage; /*!< Minimum duration of messages */
  653. static int global_vmmaxmessage; /*!< Maximum duration of message */
  654. static int global_maxsilence; /*!< Maximum silence during recording */
  655. static int global_maxgreet; /*!< Maximum length of prompts */
  656. static int global_silencethreshold = 128;
  657. static char global_mailcmd[160]; /*!< Configurable mail cmd */
  658. static char global_externnotify[160]; /*!< External notification application */
  659. static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
  660. static char default_vmformat[80];
  661. static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
  662. static int global_saydurationminfo;
  663. static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
  664. /*!\internal
  665. * \brief Default dateformat, can be overridden in configuration file */
  666. #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
  667. #define DEFAULT_CHARSET "ISO-8859-1"
  668. /* Forward declarations */
  669. static char *message_template_parse_filebody(const char *filename);
  670. static char *message_template_parse_emailbody(const char *body);
  671. static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
  672. static struct minivm_account *find_user_realtime(const char *domain, const char *username);
  673. static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  674. /*!\internal
  675. * \brief Create message template */
  676. static struct minivm_template *message_template_create(const char *name)
  677. {
  678. struct minivm_template *template;
  679. template = ast_calloc(1, sizeof(*template));
  680. if (!template)
  681. return NULL;
  682. /* Set some defaults for templates */
  683. ast_copy_string(template->name, name, sizeof(template->name));
  684. ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
  685. ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
  686. ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
  687. template->attachment = TRUE;
  688. return template;
  689. }
  690. /*!\internal
  691. * \brief Release memory allocated by message template */
  692. static void message_template_free(struct minivm_template *template)
  693. {
  694. if (template->body)
  695. ast_free(template->body);
  696. ast_free (template);
  697. }
  698. /*!\internal
  699. * \brief Build message template from configuration */
  700. static int message_template_build(const char *name, struct ast_variable *var)
  701. {
  702. struct minivm_template *template;
  703. int error = 0;
  704. template = message_template_create(name);
  705. if (!template) {
  706. ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
  707. return -1;
  708. }
  709. while (var) {
  710. ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
  711. if (!strcasecmp(var->name, "fromaddress")) {
  712. ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
  713. } else if (!strcasecmp(var->name, "fromemail")) {
  714. ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
  715. } else if (!strcasecmp(var->name, "subject")) {
  716. ast_copy_string(template->subject, var->value, sizeof(template->subject));
  717. } else if (!strcasecmp(var->name, "locale")) {
  718. ast_copy_string(template->locale, var->value, sizeof(template->locale));
  719. } else if (!strcasecmp(var->name, "attachmedia")) {
  720. template->attachment = ast_true(var->value);
  721. } else if (!strcasecmp(var->name, "dateformat")) {
  722. ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
  723. } else if (!strcasecmp(var->name, "charset")) {
  724. ast_copy_string(template->charset, var->value, sizeof(template->charset));
  725. } else if (!strcasecmp(var->name, "templatefile")) {
  726. if (template->body)
  727. ast_free(template->body);
  728. template->body = message_template_parse_filebody(var->value);
  729. if (!template->body) {
  730. ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
  731. error++;
  732. }
  733. } else if (!strcasecmp(var->name, "messagebody")) {
  734. if (template->body)
  735. ast_free(template->body);
  736. template->body = message_template_parse_emailbody(var->value);
  737. if (!template->body) {
  738. ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
  739. error++;
  740. }
  741. } else {
  742. ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
  743. error++;
  744. }
  745. var = var->next;
  746. }
  747. if (error)
  748. ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
  749. AST_LIST_LOCK(&message_templates);
  750. AST_LIST_INSERT_TAIL(&message_templates, template, list);
  751. AST_LIST_UNLOCK(&message_templates);
  752. global_stats.templates++;
  753. return error;
  754. }
  755. /*!\internal
  756. * \brief Find named template */
  757. static struct minivm_template *message_template_find(const char *name)
  758. {
  759. struct minivm_template *this, *res = NULL;
  760. if (ast_strlen_zero(name))
  761. return NULL;
  762. AST_LIST_LOCK(&message_templates);
  763. AST_LIST_TRAVERSE(&message_templates, this, list) {
  764. if (!strcasecmp(this->name, name)) {
  765. res = this;
  766. break;
  767. }
  768. }
  769. AST_LIST_UNLOCK(&message_templates);
  770. return res;
  771. }
  772. /*!\internal
  773. * \brief Clear list of templates */
  774. static void message_destroy_list(void)
  775. {
  776. struct minivm_template *this;
  777. AST_LIST_LOCK(&message_templates);
  778. while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
  779. message_template_free(this);
  780. }
  781. AST_LIST_UNLOCK(&message_templates);
  782. }
  783. /*!\internal
  784. * \brief read buffer from file (base64 conversion) */
  785. static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
  786. {
  787. int l;
  788. if (bio->ateof)
  789. return 0;
  790. if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
  791. if (ferror(fi))
  792. return -1;
  793. bio->ateof = 1;
  794. return 0;
  795. }
  796. bio->iolen= l;
  797. bio->iocp= 0;
  798. return 1;
  799. }
  800. /*!\internal
  801. * \brief read character from file to buffer (base64 conversion) */
  802. static int b64_inchar(struct b64_baseio *bio, FILE *fi)
  803. {
  804. if (bio->iocp >= bio->iolen) {
  805. if (!b64_inbuf(bio, fi))
  806. return EOF;
  807. }
  808. return bio->iobuf[bio->iocp++];
  809. }
  810. /*!\internal
  811. * \brief write buffer to file (base64 conversion) */
  812. static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
  813. {
  814. if (bio->linelength >= B64_BASELINELEN) {
  815. if (fputs(EOL,so) == EOF)
  816. return -1;
  817. bio->linelength= 0;
  818. }
  819. if (putc(((unsigned char) c), so) == EOF)
  820. return -1;
  821. bio->linelength++;
  822. return 1;
  823. }
  824. /*!\internal
  825. * \brief Encode file to base64 encoding for email attachment (base64 conversion) */
  826. static int base_encode(char *filename, FILE *so)
  827. {
  828. unsigned char dtable[B64_BASEMAXINLINE];
  829. int i,hiteof= 0;
  830. FILE *fi;
  831. struct b64_baseio bio;
  832. memset(&bio, 0, sizeof(bio));
  833. bio.iocp = B64_BASEMAXINLINE;
  834. if (!(fi = fopen(filename, "rb"))) {
  835. ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
  836. return -1;
  837. }
  838. for (i= 0; i<9; i++) {
  839. dtable[i]= 'A'+i;
  840. dtable[i+9]= 'J'+i;
  841. dtable[26+i]= 'a'+i;
  842. dtable[26+i+9]= 'j'+i;
  843. }
  844. for (i= 0; i < 8; i++) {
  845. dtable[i+18]= 'S'+i;
  846. dtable[26+i+18]= 's'+i;
  847. }
  848. for (i= 0; i < 10; i++) {
  849. dtable[52+i]= '0'+i;
  850. }
  851. dtable[62]= '+';
  852. dtable[63]= '/';
  853. while (!hiteof){
  854. unsigned char igroup[3], ogroup[4];
  855. int c,n;
  856. igroup[0]= igroup[1]= igroup[2]= 0;
  857. for (n= 0; n < 3; n++) {
  858. if ((c = b64_inchar(&bio, fi)) == EOF) {
  859. hiteof= 1;
  860. break;
  861. }
  862. igroup[n]= (unsigned char)c;
  863. }
  864. if (n> 0) {
  865. ogroup[0]= dtable[igroup[0]>>2];
  866. ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
  867. ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
  868. ogroup[3]= dtable[igroup[2]&0x3F];
  869. if (n<3) {
  870. ogroup[3]= '=';
  871. if (n<2)
  872. ogroup[2]= '=';
  873. }
  874. for (i= 0;i<4;i++)
  875. b64_ochar(&bio, ogroup[i], so);
  876. }
  877. }
  878. /* Put end of line - line feed */
  879. if (fputs(EOL, so) == EOF)
  880. return 0;
  881. fclose(fi);
  882. return 1;
  883. }
  884. static int get_date(char *s, int len)
  885. {
  886. struct ast_tm tm;
  887. struct timeval now = ast_tvnow();
  888. ast_localtime(&now, &tm, NULL);
  889. return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
  890. }
  891. /*!\internal
  892. * \brief Free user structure - if it's allocated */
  893. static void free_user(struct minivm_account *vmu)
  894. {
  895. if (vmu->chanvars)
  896. ast_variables_destroy(vmu->chanvars);
  897. ast_free(vmu);
  898. }
  899. /*!\internal
  900. * \brief Prepare for voicemail template by adding channel variables
  901. * to the channel
  902. */
  903. static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter)
  904. {
  905. char callerid[256];
  906. struct ast_variable *var;
  907. if (!channel) {
  908. ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
  909. return;
  910. }
  911. for (var = vmu->chanvars ; var ; var = var->next) {
  912. pbx_builtin_setvar_helper(channel, var->name, var->value);
  913. }
  914. /* Prepare variables for substition in email body and subject */
  915. pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
  916. pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
  917. pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
  918. pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
  919. pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
  920. pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
  921. pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
  922. pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
  923. if (!ast_strlen_zero(counter))
  924. pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
  925. }
  926. /*!\internal
  927. * \brief Set default values for Mini-Voicemail users */
  928. static void populate_defaults(struct minivm_account *vmu)
  929. {
  930. ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
  931. ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
  932. vmu->volgain = global_volgain;
  933. }
  934. /*!\internal
  935. * \brief Allocate new vm user and set default values */
  936. static struct minivm_account *mvm_user_alloc(void)
  937. {
  938. struct minivm_account *new;
  939. new = ast_calloc(1, sizeof(*new));
  940. if (!new)
  941. return NULL;
  942. populate_defaults(new);
  943. return new;
  944. }
  945. /*!\internal
  946. * \brief Clear list of users */
  947. static void vmaccounts_destroy_list(void)
  948. {
  949. struct minivm_account *this;
  950. AST_LIST_LOCK(&minivm_accounts);
  951. while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
  952. ast_free(this);
  953. AST_LIST_UNLOCK(&minivm_accounts);
  954. }
  955. /*!\internal
  956. * \brief Find user from static memory object list */
  957. static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
  958. {
  959. struct minivm_account *vmu = NULL, *cur;
  960. if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
  961. ast_log(LOG_NOTICE, "No username or domain? \n");
  962. return NULL;
  963. }
  964. ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
  965. AST_LIST_LOCK(&minivm_accounts);
  966. AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
  967. /* Is this the voicemail account we're looking for? */
  968. if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
  969. break;
  970. }
  971. AST_LIST_UNLOCK(&minivm_accounts);
  972. if (cur) {
  973. ast_debug(3, "Found account for %s@%s\n", username, domain);
  974. vmu = cur;
  975. } else
  976. vmu = find_user_realtime(domain, username);
  977. if (createtemp && !vmu) {
  978. /* Create a temporary user, send e-mail and be gone */
  979. vmu = mvm_user_alloc();
  980. ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
  981. if (vmu) {
  982. ast_copy_string(vmu->username, username, sizeof(vmu->username));
  983. ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
  984. ast_debug(1, "Created temporary account\n");
  985. }
  986. }
  987. return vmu;
  988. }
  989. /*!\internal
  990. * \brief Find user in realtime storage
  991. * \return pointer to minivm_account structure
  992. */
  993. static struct minivm_account *find_user_realtime(const char *domain, const char *username)
  994. {
  995. struct ast_variable *var;
  996. struct minivm_account *retval;
  997. char name[MAXHOSTNAMELEN];
  998. retval = mvm_user_alloc();
  999. if (!retval)
  1000. return NULL;
  1001. if (username)
  1002. ast_copy_string(retval->username, username, sizeof(retval->username));
  1003. populate_defaults(retval);
  1004. var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
  1005. if (!var) {
  1006. ast_free(retval);
  1007. return NULL;
  1008. }
  1009. snprintf(name, sizeof(name), "%s@%s", username, domain);
  1010. create_vmaccount(name, var, TRUE);
  1011. ast_variables_destroy(var);
  1012. return retval;
  1013. }
  1014. /*!\internal
  1015. * \brief Check if the string would need encoding within the MIME standard, to
  1016. * avoid confusing certain mail software that expects messages to be 7-bit
  1017. * clean.
  1018. */
  1019. static int check_mime(const char *str)
  1020. {
  1021. for (; *str; str++) {
  1022. if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
  1023. return 1;
  1024. }
  1025. }
  1026. return 0;
  1027. }
  1028. /*!\internal
  1029. * \brief Encode a string according to the MIME rules for encoding strings
  1030. * that are not 7-bit clean or contain control characters.
  1031. *
  1032. * Additionally, if the encoded string would exceed the MIME limit of 76
  1033. * characters per line, then the encoding will be broken up into multiple
  1034. * sections, separated by a space character, in order to facilitate
  1035. * breaking up the associated header across multiple lines.
  1036. *
  1037. * \param end An expandable buffer for holding the result
  1038. * \param maxlen \see ast_str
  1039. * \param charset Character set in which the result should be encoded
  1040. * \param start A string to be encoded
  1041. * \param preamble The length of the first line already used for this string,
  1042. * to ensure that each line maintains a maximum length of 76 chars.
  1043. * \param postamble the length of any additional characters appended to the
  1044. * line, used to ensure proper field wrapping.
  1045. * \return The encoded string.
  1046. */
  1047. static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *charset, const char *start, size_t preamble, size_t postamble)
  1048. {
  1049. struct ast_str *tmp = ast_str_alloca(80);
  1050. int first_section = 1;
  1051. ast_str_reset(*end);
  1052. ast_str_set(&tmp, -1, "=?%s?Q?", charset);
  1053. for (; *start; start++) {
  1054. int need_encoding = 0;
  1055. if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
  1056. need_encoding = 1;
  1057. }
  1058. if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
  1059. (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
  1060. (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
  1061. (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
  1062. /* Start new line */
  1063. ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
  1064. ast_str_set(&tmp, -1, "=?%s?Q?", charset);
  1065. first_section = 0;
  1066. }
  1067. if (need_encoding && *start == ' ') {
  1068. ast_str_append(&tmp, -1, "_");
  1069. } else if (need_encoding) {
  1070. ast_str_append(&tmp, -1, "=%hhX", *start);
  1071. } else {
  1072. ast_str_append(&tmp, -1, "%c", *start);
  1073. }
  1074. }
  1075. ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
  1076. return ast_str_buffer(*end);
  1077. }
  1078. /*!\internal
  1079. * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
  1080. * \param from The string to work with.
  1081. * \param buf The destination buffer to write the modified quoted string.
  1082. * \param maxlen Always zero. \see ast_str
  1083. *
  1084. * \return The destination string with quotes wrapped on it (the to field).
  1085. */
  1086. static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
  1087. {
  1088. const char *ptr;
  1089. /* We're only ever passing 0 to maxlen, so short output isn't possible */
  1090. ast_str_set(buf, maxlen, "\"");
  1091. for (ptr = from; *ptr; ptr++) {
  1092. if (*ptr == '"' || *ptr == '\\') {
  1093. ast_str_append(buf, maxlen, "\\%c", *ptr);
  1094. } else {
  1095. ast_str_append(buf, maxlen, "%c", *ptr);
  1096. }
  1097. }
  1098. ast_str_append(buf, maxlen, "\"");
  1099. return ast_str_buffer(*buf);
  1100. }
  1101. /*!\internal
  1102. * \brief Send voicemail with audio file as an attachment */
  1103. static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
  1104. {
  1105. FILE *p = NULL;
  1106. int pfd;
  1107. char email[256] = "";
  1108. char who[256] = "";
  1109. char date[256];
  1110. char bound[256];
  1111. char fname[PATH_MAX];
  1112. char dur[PATH_MAX];
  1113. char tmp[80] = "/tmp/astmail-XXXXXX";
  1114. char tmp2[PATH_MAX];
  1115. char newtmp[PATH_MAX]; /* Only used with volgain */
  1116. struct timeval now;
  1117. struct ast_tm tm;
  1118. struct minivm_zone *the_zone = NULL;
  1119. struct ast_channel *ast;
  1120. char *finalfilename = "";
  1121. struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
  1122. char *fromaddress;
  1123. char *fromemail;
  1124. if (!str1 || !str2) {
  1125. ast_free(str1);
  1126. ast_free(str2);
  1127. return -1;
  1128. }
  1129. if (type == MVM_MESSAGE_EMAIL) {
  1130. if (vmu && !ast_strlen_zero(vmu->email)) {
  1131. ast_copy_string(email, vmu->email, sizeof(email));
  1132. } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
  1133. snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
  1134. } else if (type == MVM_MESSAGE_PAGE) {
  1135. ast_copy_string(email, vmu->pager, sizeof(email));
  1136. }
  1137. if (ast_strlen_zero(email)) {
  1138. ast_log(LOG_WARNING, "No address to send message to.\n");
  1139. ast_free(str1);
  1140. ast_free(str2);
  1141. return -1;
  1142. }
  1143. ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
  1144. if (!strcmp(format, "wav49"))
  1145. format = "WAV";
  1146. /* If we have a gain option, process it now with sox */
  1147. if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
  1148. char tmpcmd[PATH_MAX];
  1149. int tmpfd;
  1150. ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
  1151. ast_debug(3, "newtmp: %s\n", newtmp);
  1152. tmpfd = mkstemp(newtmp);
  1153. if (tmpfd < 0) {
  1154. ast_log(LOG_WARNING, "Failed to create temporary file for volgain: %d\n", errno);
  1155. ast_free(str1);
  1156. ast_free(str2);
  1157. return -1;
  1158. }
  1159. snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
  1160. ast_safe_system(tmpcmd);
  1161. close(tmpfd);
  1162. finalfilename = newtmp;
  1163. ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
  1164. } else {
  1165. finalfilename = ast_strdupa(filename);
  1166. }
  1167. /* Create file name */
  1168. snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
  1169. if (template->attachment)
  1170. ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
  1171. /* Make a temporary file instead of piping directly to sendmail, in case the mail
  1172. command hangs */
  1173. pfd = mkstemp(tmp);
  1174. if (pfd > -1) {
  1175. p = fdopen(pfd, "w");
  1176. if (!p) {
  1177. close(pfd);
  1178. pfd = -1;
  1179. }
  1180. ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
  1181. }
  1182. if (!p) {
  1183. ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
  1184. ast_free(str1);
  1185. ast_free(str2);
  1186. return -1;
  1187. }
  1188. /* Allocate channel used for chanvar substitution */
  1189. ast = ast_dummy_channel_alloc();
  1190. if (!ast) {
  1191. ast_free(str1);
  1192. ast_free(str2);
  1193. return -1;
  1194. }
  1195. snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
  1196. /* Does this user have a timezone specified? */
  1197. if (!ast_strlen_zero(vmu->zonetag)) {
  1198. /* Find the zone in the list */
  1199. struct minivm_zone *z;
  1200. AST_LIST_LOCK(&minivm_zones);
  1201. AST_LIST_TRAVERSE(&minivm_zones, z, list) {
  1202. if (strcmp(z->name, vmu->zonetag))
  1203. continue;
  1204. the_zone = z;
  1205. }
  1206. AST_LIST_UNLOCK(&minivm_zones);
  1207. }
  1208. now = ast_tvnow();
  1209. ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
  1210. ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
  1211. /* Start printing the email to the temporary file */
  1212. fprintf(p, "Date: %s\n", date);
  1213. /* Set date format for voicemail mail */
  1214. ast_strftime(date, sizeof(date), template->dateformat, &tm);
  1215. /* Populate channel with channel variables for substitution */
  1216. prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
  1217. /* Find email address to use */
  1218. /* If there's a server e-mail adress in the account, user that, othterwise template */
  1219. fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
  1220. /* Find name to user for server e-mail */
  1221. fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
  1222. /* If needed, add hostname as domain */
  1223. if (ast_strlen_zero(fromemail))
  1224. fromemail = "asterisk";
  1225. if (strchr(fromemail, '@'))
  1226. ast_copy_string(who, fromemail, sizeof(who));
  1227. else {
  1228. char host[MAXHOSTNAMELEN];
  1229. gethostname(host, sizeof(host)-1);
  1230. snprintf(who, sizeof(who), "%s@%s", fromemail, host);
  1231. }
  1232. if (ast_strlen_zero(fromaddress)) {
  1233. fprintf(p, "From: Asterisk PBX <%s>\n", who);
  1234. } else {
  1235. ast_debug(4, "Fromaddress template: %s\n", fromaddress);
  1236. ast_str_substitute_variables(&str1, 0, ast, fromaddress);
  1237. if (check_mime(ast_str_buffer(str1))) {
  1238. int first_line = 1;
  1239. char *ptr;
  1240. ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
  1241. while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
  1242. *ptr = '\0';
  1243. fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
  1244. first_line = 0;
  1245. /* Substring is smaller, so this will never grow */
  1246. ast_str_set(&str2, 0, "%s", ptr + 1);
  1247. }
  1248. fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
  1249. } else {
  1250. fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
  1251. }
  1252. }
  1253. fprintf(p, "Message-ID: <Asterisk-%u-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
  1254. if (ast_strlen_zero(vmu->email)) {
  1255. snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
  1256. } else {
  1257. ast_copy_string(email, vmu->email, sizeof(email));
  1258. }
  1259. if (check_mime(vmu->fullname)) {
  1260. int first_line = 1;
  1261. char *ptr;
  1262. ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
  1263. while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
  1264. *ptr = '\0';
  1265. fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
  1266. first_line = 0;
  1267. /* Substring is smaller, so this will never grow */
  1268. ast_str_set(&str2, 0, "%s", ptr + 1);
  1269. }
  1270. fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
  1271. } else {
  1272. fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
  1273. }
  1274. if (!ast_strlen_zero(template->subject)) {
  1275. ast_str_substitute_variables(&str1, 0, ast, template->subject);
  1276. if (check_mime(ast_str_buffer(str1))) {
  1277. int first_line = 1;
  1278. char *ptr;
  1279. ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
  1280. while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
  1281. *ptr = '\0';
  1282. fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
  1283. first_line = 0;
  1284. /* Substring is smaller, so this will never grow */
  1285. ast_str_set(&str2, 0, "%s", ptr + 1);
  1286. }
  1287. fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
  1288. } else {
  1289. fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
  1290. }
  1291. } else {
  1292. fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
  1293. ast_debug(1, "Using default subject for this email \n");
  1294. }
  1295. if (option_debug > 2)
  1296. fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
  1297. fprintf(p, "MIME-Version: 1.0\n");
  1298. /* Something unique. */
  1299. snprintf(bound, sizeof(bound), "voicemail_%s%d%u", vmu->username, (int)getpid(), (unsigned int)ast_random());
  1300. fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
  1301. fprintf(p, "--%s\n", bound);
  1302. fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
  1303. if (!ast_strlen_zero(template->body)) {
  1304. ast_str_substitute_variables(&str1, 0, ast, template->body);
  1305. ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
  1306. fprintf(p, "%s\n", ast_str_buffer(str1));
  1307. } else {
  1308. fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
  1309. "in mailbox %s from %s, on %s so you might\n"
  1310. "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
  1311. dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
  1312. ast_debug(3, "Using default message body (no template)\n-----\n");
  1313. }
  1314. /* Eww. We want formats to tell us their own MIME type */
  1315. if (template->attachment) {
  1316. char *ctype = "audio/x-";
  1317. ast_debug(3, "Attaching file to message: %s\n", fname);
  1318. if (!strcasecmp(format, "ogg"))
  1319. ctype = "application/";
  1320. fprintf(p, "--%s\n", bound);
  1321. fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
  1322. fprintf(p, "Content-Transfer-Encoding: base64\n");
  1323. fprintf(p, "Content-Description: Voicemail sound attachment.\n");
  1324. fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
  1325. base_encode(fname, p);
  1326. fprintf(p, "\n\n--%s--\n.\n", bound);
  1327. }
  1328. fclose(p);
  1329. snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
  1330. ast_safe_system(tmp2);
  1331. ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
  1332. ast_debug(3, "Actual command used: %s\n", tmp2);
  1333. ast = ast_channel_unref(ast);
  1334. ast_free(str1);
  1335. ast_free(str2);
  1336. return 0;
  1337. }
  1338. /*!\internal
  1339. * \brief Create directory based on components */
  1340. static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
  1341. {
  1342. return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
  1343. }
  1344. /*!\internal
  1345. * \brief Checks if directory exists. Does not create directory, but builds string in dest
  1346. * \param dest String. base directory.
  1347. * \param len Int. Length base directory string.
  1348. * \param domain String. Ignored if is null or empty string.
  1349. * \param username String. Ignored if is null or empty string.
  1350. * \param folder String. Ignored if is null or empty string.
  1351. * \return 0 on failure, 1 on success.
  1352. */
  1353. static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
  1354. {
  1355. struct stat filestat;
  1356. make_dir(dest, len, domain, username, folder ? folder : "");
  1357. if (stat(dest, &filestat)== -1)
  1358. return FALSE;
  1359. else
  1360. return TRUE;
  1361. }
  1362. /*!\internal
  1363. * \brief basically mkdir -p $dest/$domain/$username/$folder
  1364. * \param dest String. base directory.
  1365. * \param len Length of directory string
  1366. * \param domain String. Ignored if is null or empty string.
  1367. * \param folder String. Ignored if is null or empty string.
  1368. * \param username String. Ignored if is null or empty string.
  1369. * \return -1 on failure, 0 on success.
  1370. */
  1371. static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
  1372. {
  1373. int res;
  1374. make_dir(dest, len, domain, username, folder);
  1375. if ((res = ast_mkdir(dest, 0777))) {
  1376. ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
  1377. return -1;
  1378. }
  1379. ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
  1380. return 0;
  1381. }
  1382. /*!\internal
  1383. * \brief Play intro message before recording voicemail
  1384. */
  1385. static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
  1386. {
  1387. int res;
  1388. char fn[PATH_MAX];
  1389. ast_debug(2, "Still preparing to play message ...\n");
  1390. snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
  1391. if (ast_fileexists(fn, NULL, NULL) > 0) {
  1392. res = ast_streamfile(chan, fn, ast_channel_language(chan));
  1393. if (res)
  1394. return -1;
  1395. res = ast_waitstream(chan, ecodes);
  1396. if (res)
  1397. return res;
  1398. } else {
  1399. int numericusername = 1;
  1400. char *i = username;
  1401. ast_debug(2, "No personal prompts. Using default prompt set for language\n");
  1402. while (*i) {
  1403. ast_debug(2, "Numeric? Checking %c\n", *i);
  1404. if (!isdigit(*i)) {
  1405. numericusername = FALSE;
  1406. break;
  1407. }
  1408. i++;
  1409. }
  1410. if (numericusername) {
  1411. if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
  1412. return -1;
  1413. if ((res = ast_waitstream(chan, ecodes)))
  1414. return res;
  1415. res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
  1416. if (res)
  1417. return res;
  1418. } else {
  1419. if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
  1420. return -1;
  1421. if ((res = ast_waitstream(chan, ecodes)))
  1422. return res;
  1423. }
  1424. }
  1425. res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
  1426. if (res)
  1427. return -1;
  1428. res = ast_waitstream(chan, ecodes);
  1429. return res;
  1430. }
  1431. /*!\internal
  1432. * \brief Delete media files and attribute file */
  1433. static int vm_delete(char *file)
  1434. {
  1435. int res;
  1436. ast_debug(1, "Deleting voicemail file %s\n", file);
  1437. res = unlink(file); /* Remove the meta data file */
  1438. res |= ast_filedelete(file, NULL); /* remove the media file */
  1439. return res;
  1440. }
  1441. /*!\internal
  1442. * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
  1443. static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
  1444. int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
  1445. signed char record_gain)
  1446. {
  1447. int cmd = 0;
  1448. int max_attempts = 3;
  1449. int attempts = 0;
  1450. int recorded = 0;
  1451. int message_exists = 0;
  1452. signed char zero_gain = 0;
  1453. char *acceptdtmf = "#";
  1454. char *canceldtmf = "";
  1455. /* Note that urgent and private are for flagging messages as such in the future */
  1456. /* barf if no pointer passed to store duration in */
  1457. if (duration == NULL) {
  1458. ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
  1459. return -1;
  1460. }
  1461. cmd = '3'; /* Want to start by recording */
  1462. while ((cmd >= 0) && (cmd != 't')) {
  1463. switch (cmd) {
  1464. case '1':
  1465. ast_verb(3, "Saving message as is\n");
  1466. ast_stream_and_wait(chan, "vm-msgsaved", "");
  1467. cmd = 't';
  1468. break;
  1469. case '2':
  1470. /* Review */
  1471. ast_verb(3, "Reviewing the message\n");
  1472. ast_streamfile(chan, recordfile, ast_channel_language(chan));
  1473. cmd = ast_waitstream(chan, AST_DIGIT_ANY);
  1474. break;
  1475. case '3':
  1476. message_exists = 0;
  1477. /* Record */
  1478. if (recorded == 1)
  1479. ast_verb(3, "Re-recording the message\n");
  1480. else
  1481. ast_verb(3, "Recording the message\n");
  1482. if (recorded && outsidecaller)
  1483. cmd = ast_play_and_wait(chan, "beep");
  1484. recorded = 1;
  1485. /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */
  1486. if (record_gain)
  1487. ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
  1488. if (ast_test_flag(vmu, MVM_OPERATOR))
  1489. canceldtmf = "0";
  1490. cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
  1491. if (record_gain)
  1492. ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
  1493. if (cmd == -1) /* User has hung up, no options to give */
  1494. return cmd;
  1495. if (cmd == '0')
  1496. break;
  1497. else if (cmd == '*')
  1498. break;
  1499. else {
  1500. /* If all is well, a message exists */
  1501. message_exists = 1;
  1502. cmd = 0;
  1503. }
  1504. break;
  1505. case '4':
  1506. case '5':
  1507. case '6':
  1508. case '7':
  1509. case '8':
  1510. case '9':
  1511. case '*':
  1512. case '#':
  1513. cmd = ast_play_and_wait(chan, "vm-sorry");
  1514. break;
  1515. case '0':
  1516. if(!ast_test_flag(vmu, MVM_OPERATOR)) {
  1517. cmd = ast_play_and_wait(chan, "vm-sorry");
  1518. break;
  1519. }
  1520. if (message_exists || recorded) {
  1521. cmd = ast_play_and_wait(chan, "vm-saveoper");
  1522. if (!cmd)
  1523. cmd = ast_waitfordigit(chan, 3000);
  1524. if (cmd == '1') {
  1525. ast_play_and_wait(chan, "vm-msgsaved");
  1526. cmd = '0';
  1527. } else {
  1528. ast_play_and_wait(chan, "vm-deleted");
  1529. vm_delete(recordfile);
  1530. cmd = '0';
  1531. }
  1532. }
  1533. return cmd;
  1534. default:
  1535. /* If the caller is an ouside caller, and the review option is enabled,
  1536. allow them to review the message, but let the owner of the box review
  1537. their OGM's */
  1538. if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
  1539. return cmd;
  1540. if (message_exists) {
  1541. cmd = ast_play_and_wait(chan, "vm-review");
  1542. } else {
  1543. cmd = ast_play_and_wait(chan, "vm-torerecord");
  1544. if (!cmd)
  1545. cmd = ast_waitfordigit(chan, 600);
  1546. }
  1547. if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
  1548. cmd = ast_play_and_wait(chan, "vm-reachoper");
  1549. if (!cmd)
  1550. cmd = ast_waitfordigit(chan, 600);
  1551. }
  1552. if (!cmd)
  1553. cmd = ast_waitfordigit(chan, 6000);
  1554. if (!cmd) {
  1555. attempts++;
  1556. }
  1557. if (attempts > max_attempts) {
  1558. cmd = 't';
  1559. }
  1560. }
  1561. }
  1562. if (outsidecaller)
  1563. ast_play_and_wait(chan, "vm-goodbye");
  1564. if (cmd == 't')
  1565. cmd = 0;
  1566. return cmd;
  1567. }
  1568. /*! \brief Run external notification for voicemail message */
  1569. static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
  1570. {
  1571. char arguments[BUFSIZ];
  1572. if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
  1573. return;
  1574. snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
  1575. ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
  1576. vmu->username, vmu->domain,
  1577. (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str)
  1578. ? ast_channel_caller(chan)->id.name.str : "",
  1579. (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str)
  1580. ? ast_channel_caller(chan)->id.number.str : "");
  1581. ast_debug(1, "Executing: %s\n", arguments);
  1582. ast_safe_system(arguments);
  1583. }
  1584. /*!\internal
  1585. * \brief Send message to voicemail account owner */
  1586. static int notify_new_message(struct ast_channel *chan, const char *templatename, struct minivm_account *vmu, const char *filename, long duration, const char *format, char *cidnum, char *cidname)
  1587. {
  1588. RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
  1589. RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
  1590. RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
  1591. char *stringp;
  1592. struct minivm_template *etemplate;
  1593. char *messageformat;
  1594. int res = 0;
  1595. char oldlocale[100];
  1596. const char *counter;
  1597. if (!ast_strlen_zero(vmu->attachfmt)) {
  1598. if (strstr(format, vmu->attachfmt)) {
  1599. format = vmu->attachfmt;
  1600. } else {
  1601. ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'. Falling back to default format for '%s@%s'.\n", vmu->attachfmt, format, vmu->username, vmu->domain);
  1602. }
  1603. }
  1604. etemplate = message_template_find(vmu->etemplate);
  1605. if (!etemplate)
  1606. etemplate = message_template_find(templatename);
  1607. if (!etemplate)
  1608. etemplate = message_template_find("email-default");
  1609. /* Attach only the first format */
  1610. stringp = messageformat = ast_strdupa(format);
  1611. strsep(&stringp, "|");
  1612. if (!ast_strlen_zero(etemplate->locale)) {
  1613. char *new_locale;
  1614. ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
  1615. ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
  1616. new_locale = setlocale(LC_TIME, etemplate->locale);
  1617. if (new_locale == NULL) {
  1618. ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
  1619. }
  1620. }
  1621. /* Read counter if available */
  1622. ast_channel_lock(chan);
  1623. if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
  1624. counter = ast_strdupa(counter);
  1625. }
  1626. ast_channel_unlock(chan);
  1627. if (ast_strlen_zero(counter)) {
  1628. ast_debug(2, "MVM_COUNTER not found\n");
  1629. } else {
  1630. ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
  1631. }
  1632. res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
  1633. if (res == 0 && !ast_strlen_zero(vmu->pager)) {
  1634. /* Find template for paging */
  1635. etemplate = message_template_find(vmu->ptemplate);
  1636. if (!etemplate)
  1637. etemplate = message_template_find("pager-default");
  1638. if (etemplate->locale) {
  1639. ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
  1640. setlocale(LC_TIME, etemplate->locale);
  1641. }
  1642. res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
  1643. }
  1644. mwi_state = ast_mwi_create(vmu->username, vmu->domain);
  1645. if (!mwi_state) {
  1646. goto notify_cleanup;
  1647. }
  1648. mwi_state->snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
  1649. json_object = ast_json_pack("{s: s, s: s}",
  1650. "Event", "MiniVoiceMail"
  1651. "Action", "SentNotification",
  1652. "Counter", counter);
  1653. if (!json_object) {
  1654. goto notify_cleanup;
  1655. }
  1656. message = ast_mwi_blob_create(mwi_state, ast_mwi_vm_app_type(), json_object);
  1657. if (!message) {
  1658. goto notify_cleanup;
  1659. }
  1660. stasis_publish(ast_mwi_topic(mwi_state->uniqueid), message);
  1661. notify_cleanup:
  1662. run_externnotify(chan, vmu); /* Run external notification */
  1663. if (etemplate->locale) {
  1664. setlocale(LC_TIME, oldlocale); /* Rest to old locale */
  1665. }
  1666. return res;
  1667. }
  1668. /*!\internal
  1669. * \brief Record voicemail message, store into file prepared for sending e-mail */
  1670. static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
  1671. {
  1672. char tmptxtfile[PATH_MAX];
  1673. char callerid[256];
  1674. FILE *txt;
  1675. int res = 0, txtdes;
  1676. int duration = 0;
  1677. int sound_duration = 0;
  1678. char date[256];
  1679. char tmpdir[PATH_MAX];
  1680. char ext_context[256] = "";
  1681. char fmt[80];
  1682. char *domain;
  1683. char tmp[256] = "";
  1684. struct minivm_account *vmu;
  1685. int userdir;
  1686. ast_copy_string(tmp, username, sizeof(tmp));
  1687. username = tmp;
  1688. domain = strchr(tmp, '@');
  1689. if (domain) {
  1690. *domain = '\0';
  1691. domain++;
  1692. }
  1693. if (!(vmu = find_account(domain, username, TRUE))) {
  1694. /* We could not find user, let's exit */
  1695. ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
  1696. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1697. return 0;
  1698. }
  1699. /* Setup pre-file if appropriate */
  1700. if (strcmp(vmu->domain, "localhost"))
  1701. snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
  1702. else
  1703. ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
  1704. /* The meat of recording the message... All the announcements and beeps have been played*/
  1705. if (ast_strlen_zero(vmu->attachfmt))
  1706. ast_copy_string(fmt, default_vmformat, sizeof(fmt));
  1707. else
  1708. ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
  1709. if (ast_strlen_zero(fmt)) {
  1710. ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
  1711. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1712. return res;
  1713. }
  1714. userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
  1715. /* If we have no user directory, use generic temporary directory */
  1716. if (!userdir) {
  1717. create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
  1718. ast_debug(3, "Creating temporary directory %s\n", tmpdir);
  1719. }
  1720. snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
  1721. /* XXX This file needs to be in temp directory */
  1722. txtdes = mkstemp(tmptxtfile);
  1723. if (txtdes < 0) {
  1724. ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
  1725. res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
  1726. if (!res)
  1727. res = ast_waitstream(chan, "");
  1728. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1729. return res;
  1730. }
  1731. if (res >= 0) {
  1732. /* Unless we're *really* silent, try to send the beep */
  1733. res = ast_streamfile(chan, "beep", ast_channel_language(chan));
  1734. if (!res)
  1735. res = ast_waitstream(chan, "");
  1736. }
  1737. /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
  1738. /* Store information */
  1739. ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
  1740. res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
  1741. txt = fdopen(txtdes, "w+");
  1742. if (!txt) {
  1743. ast_log(LOG_WARNING, "Error opening text file for output\n");
  1744. } else {
  1745. struct ast_tm tm;
  1746. struct timeval now = ast_tvnow();
  1747. char timebuf[30];
  1748. char logbuf[BUFSIZ];
  1749. get_date(date, sizeof(date));
  1750. ast_localtime(&now, &tm, NULL);
  1751. ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
  1752. ast_callerid_merge(callerid, sizeof(callerid),
  1753. S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
  1754. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
  1755. "Unknown");
  1756. snprintf(logbuf, sizeof(logbuf),
  1757. /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
  1758. "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
  1759. username,
  1760. ast_channel_context(chan),
  1761. ast_channel_macrocontext(chan),
  1762. ast_channel_exten(chan),
  1763. ast_channel_priority(chan),
  1764. ast_channel_name(chan),
  1765. callerid,
  1766. date,
  1767. timebuf,
  1768. duration,
  1769. duration < global_vmminmessage ? "IGNORED" : "OK",
  1770. vmu->accountcode
  1771. );
  1772. fprintf(txt, "%s", logbuf);
  1773. if (minivmlogfile) {
  1774. ast_mutex_lock(&minivmloglock);
  1775. fprintf(minivmlogfile, "%s", logbuf);
  1776. ast_mutex_unlock(&minivmloglock);
  1777. }
  1778. if (sound_duration < global_vmminmessage) {
  1779. ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
  1780. fclose(txt);
  1781. ast_filedelete(tmptxtfile, NULL);
  1782. unlink(tmptxtfile);
  1783. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1784. return 0;
  1785. }
  1786. fclose(txt); /* Close log file */
  1787. if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
  1788. ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
  1789. unlink(tmptxtfile);
  1790. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1791. if(ast_test_flag(vmu, MVM_ALLOCED))
  1792. free_user(vmu);
  1793. return 0;
  1794. }
  1795. /* Set channel variables for the notify application */
  1796. pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
  1797. snprintf(timebuf, sizeof(timebuf), "%d", duration);
  1798. pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
  1799. pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
  1800. }
  1801. global_stats.lastreceived = ast_tvnow();
  1802. global_stats.receivedmessages++;
  1803. #if 0
  1804. /* Go ahead and delete audio files from system, they're not needed any more */
  1805. if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
  1806. ast_filedelete(tmptxtfile, NULL);
  1807. /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
  1808. ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
  1809. }
  1810. #endif
  1811. if (res > 0)
  1812. res = 0;
  1813. if(ast_test_flag(vmu, MVM_ALLOCED))
  1814. free_user(vmu);
  1815. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
  1816. return res;
  1817. }
  1818. /*!\internal
  1819. * \brief Queue a message waiting event */
  1820. static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
  1821. {
  1822. char *mailbox, *context;
  1823. mailbox = ast_strdupa(mbx);
  1824. context = ast_strdupa(ctx);
  1825. if (ast_strlen_zero(context)) {
  1826. context = "default";
  1827. }
  1828. ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
  1829. }
  1830. /*!\internal
  1831. * \brief Send MWI using interal Asterisk event subsystem */
  1832. static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
  1833. {
  1834. int argc;
  1835. char *argv[4];
  1836. int res = 0;
  1837. char *tmpptr;
  1838. char tmp[PATH_MAX];
  1839. char *mailbox;
  1840. char *domain;
  1841. if (ast_strlen_zero(data)) {
  1842. ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
  1843. return -1;
  1844. }
  1845. tmpptr = ast_strdupa((char *)data);
  1846. argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
  1847. if (argc < 4) {
  1848. ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
  1849. return -1;
  1850. }
  1851. ast_copy_string(tmp, argv[0], sizeof(tmp));
  1852. mailbox = tmp;
  1853. domain = strchr(tmp, '@');
  1854. if (domain) {
  1855. *domain = '\0';
  1856. domain++;
  1857. }
  1858. if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
  1859. ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
  1860. return -1;
  1861. }
  1862. queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
  1863. return res;
  1864. }
  1865. /*!\internal
  1866. * \brief Notify voicemail account owners - either generic template or user specific */
  1867. static int minivm_notify_exec(struct ast_channel *chan, const char *data)
  1868. {
  1869. int argc;
  1870. char *argv[2];
  1871. int res = 0;
  1872. char tmp[PATH_MAX];
  1873. char *domain;
  1874. char *tmpptr;
  1875. struct minivm_account *vmu;
  1876. char *username;
  1877. const char *template = "";
  1878. const char *filename;
  1879. const char *format;
  1880. const char *duration_string;
  1881. if (ast_strlen_zero(data)) {
  1882. ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
  1883. return -1;
  1884. }
  1885. tmpptr = ast_strdupa((char *)data);
  1886. argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
  1887. if (argc == 2 && !ast_strlen_zero(argv[1]))
  1888. template = argv[1];
  1889. ast_copy_string(tmp, argv[0], sizeof(tmp));
  1890. username = tmp;
  1891. domain = strchr(tmp, '@');
  1892. if (domain) {
  1893. *domain = '\0';
  1894. domain++;
  1895. }
  1896. if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
  1897. ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
  1898. return -1;
  1899. }
  1900. if(!(vmu = find_account(domain, username, TRUE))) {
  1901. /* We could not find user, let's exit */
  1902. ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
  1903. pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
  1904. return -1;
  1905. }
  1906. ast_channel_lock(chan);
  1907. if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
  1908. filename = ast_strdupa(filename);
  1909. }
  1910. ast_channel_unlock(chan);
  1911. /* Notify of new message to e-mail and pager */
  1912. if (!ast_strlen_zero(filename)) {
  1913. ast_channel_lock(chan);
  1914. if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
  1915. format = ast_strdupa(format);
  1916. }
  1917. if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
  1918. duration_string = ast_strdupa(duration_string);
  1919. }
  1920. ast_channel_unlock(chan);
  1921. res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
  1922. format,
  1923. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
  1924. S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
  1925. }
  1926. pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
  1927. if(ast_test_flag(vmu, MVM_ALLOCED))
  1928. free_user(vmu);
  1929. /* Ok, we're ready to rock and roll. Return to dialplan */
  1930. return res;
  1931. }
  1932. /*!\internal
  1933. * \brief Dialplan function to record voicemail */
  1934. static int minivm_record_exec(struct ast_channel *chan, const char *data)
  1935. {
  1936. int res = 0;
  1937. char *tmp;
  1938. struct leave_vm_options leave_options;
  1939. int argc;
  1940. char *argv[2];
  1941. struct ast_flags flags = { 0 };
  1942. char *opts[OPT_ARG_ARRAY_SIZE];
  1943. memset(&leave_options, 0, sizeof(leave_options));
  1944. /* Answer channel if it's not already answered */
  1945. if (ast_channel_state(chan) != AST_STATE_UP)
  1946. ast_answer(chan);
  1947. if (ast_strlen_zero(data)) {
  1948. ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
  1949. return -1;
  1950. }
  1951. tmp = ast_strdupa((char *)data);
  1952. argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
  1953. if (argc == 2) {
  1954. if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
  1955. return -1;
  1956. }
  1957. ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
  1958. if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
  1959. int gain;
  1960. if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
  1961. ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
  1962. return -1;
  1963. } else
  1964. leave_options.record_gain = (signed char) gain;
  1965. }
  1966. }
  1967. /* Now run the appliation and good luck to you! */
  1968. res = leave_voicemail(chan, argv[0], &leave_options);
  1969. if (res == ERROR_LOCK_PATH) {
  1970. ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
  1971. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
  1972. res = 0;
  1973. }
  1974. pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
  1975. return res;
  1976. }
  1977. /*!\internal
  1978. * \brief Play voicemail prompts - either generic or user specific */
  1979. static int minivm_greet_exec(struct ast_channel *chan, const char *data)
  1980. {
  1981. struct leave_vm_options leave_options = { 0, '\0'};
  1982. int argc;
  1983. char *argv[2];
  1984. struct ast_flags flags = { 0 };
  1985. char *opts[OPT_ARG_ARRAY_SIZE];
  1986. int res = 0;
  1987. int ausemacro = 0;
  1988. int ousemacro = 0;
  1989. int ouseexten = 0;
  1990. char tmp[PATH_MAX];
  1991. char dest[PATH_MAX];
  1992. char prefile[PATH_MAX] = "";
  1993. char tempfile[PATH_MAX] = "";
  1994. char ext_context[256] = "";
  1995. char *domain;
  1996. char ecodes[16] = "#";
  1997. char *tmpptr;
  1998. struct minivm_account *vmu;
  1999. char *username = argv[0];
  2000. if (ast_strlen_zero(data)) {
  2001. ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
  2002. return -1;
  2003. }
  2004. tmpptr = ast_strdupa((char *)data);
  2005. argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
  2006. if (argc == 2) {
  2007. if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
  2008. return -1;
  2009. ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
  2010. }
  2011. ast_copy_string(tmp, argv[0], sizeof(tmp));
  2012. username = tmp;
  2013. domain = strchr(tmp, '@');
  2014. if (domain) {
  2015. *domain = '\0';
  2016. domain++;
  2017. }
  2018. if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
  2019. ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
  2020. return -1;
  2021. }
  2022. ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
  2023. if (!(vmu = find_account(domain, username, TRUE))) {
  2024. ast_log(LOG_ERROR, "Could not allocate memory. \n");
  2025. return -1;
  2026. }
  2027. /* Answer channel if it's not already answered */
  2028. if (ast_channel_state(chan) != AST_STATE_UP)
  2029. ast_answer(chan);
  2030. /* Setup pre-file if appropriate */
  2031. if (strcmp(vmu->domain, "localhost"))
  2032. snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
  2033. else
  2034. ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
  2035. if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
  2036. res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
  2037. if (res)
  2038. snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
  2039. } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
  2040. res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
  2041. if (res)
  2042. snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
  2043. }
  2044. /* Check for temporary greeting - it overrides busy and unavail */
  2045. snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
  2046. if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
  2047. ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
  2048. ast_copy_string(prefile, tempfile, sizeof(prefile));
  2049. }
  2050. ast_debug(2, "Preparing to play message ...\n");
  2051. /* Check current or macro-calling context for special extensions */
  2052. if (ast_test_flag(vmu, MVM_OPERATOR)) {
  2053. if (!ast_strlen_zero(vmu->exit)) {
  2054. if (ast_exists_extension(chan, vmu->exit, "o", 1,
  2055. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2056. strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
  2057. ouseexten = 1;
  2058. }
  2059. } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
  2060. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2061. strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
  2062. ouseexten = 1;
  2063. }
  2064. else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
  2065. && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
  2066. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2067. strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
  2068. ousemacro = 1;
  2069. }
  2070. }
  2071. if (!ast_strlen_zero(vmu->exit)) {
  2072. if (ast_exists_extension(chan, vmu->exit, "a", 1,
  2073. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2074. strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
  2075. }
  2076. } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
  2077. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2078. strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
  2079. } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
  2080. && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
  2081. S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
  2082. strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
  2083. ausemacro = 1;
  2084. }
  2085. res = 0; /* Reset */
  2086. /* Play the beginning intro if desired */
  2087. if (!ast_strlen_zero(prefile)) {
  2088. if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1)
  2089. res = ast_waitstream(chan, ecodes);
  2090. } else {
  2091. ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
  2092. res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
  2093. }
  2094. if (res < 0) {
  2095. ast_debug(2, "Hang up during prefile playback\n");
  2096. pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
  2097. if(ast_test_flag(vmu, MVM_ALLOCED))
  2098. free_user(vmu);
  2099. return -1;
  2100. }
  2101. if (res == '#') {
  2102. /* On a '#' we skip the instructions */
  2103. ast_set_flag(&leave_options, OPT_SILENT);
  2104. res = 0;
  2105. }
  2106. if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
  2107. res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
  2108. if (!res)
  2109. res = ast_waitstream(chan, ecodes);
  2110. if (res == '#') {
  2111. ast_set_flag(&leave_options, OPT_SILENT);
  2112. res = 0;
  2113. }
  2114. }
  2115. if (res > 0)
  2116. ast_stopstream(chan);
  2117. /* Check for a '*' here in case the caller wants to escape from voicemail to something
  2118. other than the operator -- an automated attendant or mailbox login for example */
  2119. if (res == '*') {
  2120. ast_channel_exten_set(chan, "a");
  2121. if (!ast_strlen_zero(vmu->exit)) {
  2122. ast_channel_context_set(chan, vmu->exit);
  2123. } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
  2124. ast_channel_context_set(chan, ast_channel_macrocontext(chan));
  2125. }
  2126. ast_channel_priority_set(chan, 0);
  2127. pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
  2128. res = 0;
  2129. } else if (res == '0') { /* Check for a '0' here */
  2130. if(ouseexten || ousemacro) {
  2131. ast_channel_exten_set(chan, "o");
  2132. if (!ast_strlen_zero(vmu->exit)) {
  2133. ast_channel_context_set(chan, vmu->exit);
  2134. } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
  2135. ast_channel_context_set(chan, ast_channel_macrocontext(chan));
  2136. }
  2137. ast_play_and_wait(chan, "transfer");
  2138. ast_channel_priority_set(chan, 0);
  2139. pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
  2140. }
  2141. res = 0;
  2142. } else if (res < 0) {
  2143. pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
  2144. res = -1;
  2145. } else
  2146. pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
  2147. if(ast_test_flag(vmu, MVM_ALLOCED))
  2148. free_user(vmu);
  2149. /* Ok, we're ready to rock and roll. Return to dialplan */
  2150. return res;
  2151. }
  2152. /*!\internal
  2153. * \brief Dialplan application to delete voicemail */
  2154. static int minivm_delete_exec(struct ast_channel *chan, const char *data)
  2155. {
  2156. int res = 0;
  2157. char filename[BUFSIZ];
  2158. if (!ast_strlen_zero(data)) {
  2159. ast_copy_string(filename, (char *) data, sizeof(filename));
  2160. } else {
  2161. ast_channel_lock(chan);
  2162. ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
  2163. ast_channel_unlock(chan);
  2164. }
  2165. if (ast_strlen_zero(filename)) {
  2166. ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
  2167. return res;
  2168. }
  2169. /* Go ahead and delete audio files from system, they're not needed any more */
  2170. /* We should look for both audio and text files here */
  2171. if (ast_fileexists(filename, NULL, NULL) > 0) {
  2172. res = vm_delete(filename);
  2173. if (res) {
  2174. ast_debug(2, "Can't delete file: %s\n", filename);
  2175. pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
  2176. } else {
  2177. ast_debug(2, "Deleted voicemail file :: %s \n", filename);
  2178. pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
  2179. }
  2180. } else {
  2181. ast_debug(2, "Filename does not exist: %s\n", filename);
  2182. pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
  2183. }
  2184. return res;
  2185. }
  2186. /*! \brief Record specific messages for voicemail account */
  2187. static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
  2188. {
  2189. int argc = 0;
  2190. char *argv[2];
  2191. char filename[PATH_MAX];
  2192. char tmp[PATH_MAX];
  2193. char *domain;
  2194. char *tmpptr = NULL;
  2195. struct minivm_account *vmu;
  2196. char *username;
  2197. struct ast_flags flags = { 0 };
  2198. char *opts[OPT_ARG_ARRAY_SIZE];
  2199. int error = FALSE;
  2200. char *message = NULL;
  2201. char *prompt = NULL;
  2202. int duration;
  2203. if (ast_strlen_zero(data)) {
  2204. ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
  2205. error = TRUE;
  2206. } else {
  2207. tmpptr = ast_strdupa((char *)data);
  2208. argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
  2209. }
  2210. if (argc <=1) {
  2211. ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
  2212. error = TRUE;
  2213. }
  2214. if (!error && strlen(argv[1]) > 1) {
  2215. ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
  2216. error = TRUE;
  2217. }
  2218. if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
  2219. ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
  2220. error = TRUE;
  2221. }
  2222. if (error) {
  2223. pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
  2224. return -1;
  2225. }
  2226. ast_copy_string(tmp, argv[0], sizeof(tmp));
  2227. username = tmp;
  2228. domain = strchr(tmp, '@');
  2229. if (domain) {
  2230. *domain = '\0';
  2231. domain++;
  2232. }
  2233. if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
  2234. ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
  2235. pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
  2236. return -1;
  2237. }
  2238. if(!(vmu = find_account(domain, username, TRUE))) {
  2239. /* We could not find user, let's exit */
  2240. ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
  2241. pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
  2242. return -1;
  2243. }
  2244. /* Answer channel if it's not already answered */
  2245. if (ast_channel_state(chan) != AST_STATE_UP)
  2246. ast_answer(chan);
  2247. /* Here's where the action is */
  2248. if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
  2249. message = "busy";
  2250. prompt = "vm-rec-busy";
  2251. } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
  2252. message = "unavailable";
  2253. prompt = "vm-rec-unv";
  2254. } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
  2255. message = "temp";
  2256. prompt = "vm-rec-temp";
  2257. } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
  2258. message = "greet";
  2259. prompt = "vm-rec-name";
  2260. }
  2261. snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
  2262. /* Maybe we should check the result of play_record_review ? */
  2263. play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
  2264. ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
  2265. if(ast_test_flag(vmu, MVM_ALLOCED))
  2266. free_user(vmu);
  2267. pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
  2268. /* Ok, we're ready to rock and roll. Return to dialplan */
  2269. return 0;
  2270. }
  2271. /*! \brief Append new mailbox to mailbox list from configuration file */
  2272. static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
  2273. {
  2274. struct minivm_account *vmu;
  2275. char *domain;
  2276. char *username;
  2277. char accbuf[BUFSIZ];
  2278. ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
  2279. ast_copy_string(accbuf, name, sizeof(accbuf));
  2280. username = accbuf;
  2281. domain = strchr(accbuf, '@');
  2282. if (domain) {
  2283. *domain = '\0';
  2284. domain++;
  2285. }
  2286. if (ast_strlen_zero(domain)) {
  2287. ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
  2288. return 0;
  2289. }
  2290. ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
  2291. /* Allocate user account */
  2292. vmu = ast_calloc(1, sizeof(*vmu));
  2293. if (!vmu)
  2294. return 0;
  2295. ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
  2296. ast_copy_string(vmu->username, username, sizeof(vmu->username));
  2297. populate_defaults(vmu);
  2298. ast_debug(3, "...Configuring account %s\n", name);
  2299. while (var) {
  2300. ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
  2301. if (!strcasecmp(var->name, "serveremail")) {
  2302. ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
  2303. } else if (!strcasecmp(var->name, "email")) {
  2304. ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
  2305. } else if (!strcasecmp(var->name, "accountcode")) {
  2306. ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
  2307. } else if (!strcasecmp(var->name, "pincode")) {
  2308. ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
  2309. } else if (!strcasecmp(var->name, "domain")) {
  2310. ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
  2311. } else if (!strcasecmp(var->name, "language")) {
  2312. ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
  2313. } else if (!strcasecmp(var->name, "timezone")) {
  2314. ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
  2315. } else if (!strcasecmp(var->name, "externnotify")) {
  2316. ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
  2317. } else if (!strcasecmp(var->name, "etemplate")) {
  2318. ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
  2319. } else if (!strcasecmp(var->name, "ptemplate")) {
  2320. ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
  2321. } else if (!strcasecmp(var->name, "fullname")) {
  2322. ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
  2323. } else if (!strcasecmp(var->name, "setvar")) {
  2324. char *varval;
  2325. char *varname = ast_strdupa(var->value);
  2326. struct ast_variable *tmpvar;
  2327. if ((varval = strchr(varname, '='))) {
  2328. *varval = '\0';
  2329. varval++;
  2330. if ((tmpvar = ast_variable_new(varname, varval, ""))) {
  2331. tmpvar->next = vmu->chanvars;
  2332. vmu->chanvars = tmpvar;
  2333. }
  2334. }
  2335. } else if (!strcasecmp(var->name, "pager")) {
  2336. ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
  2337. } else if (!strcasecmp(var->name, "volgain")) {
  2338. sscanf(var->value, "%30lf", &vmu->volgain);
  2339. } else {
  2340. ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
  2341. }
  2342. var = var->next;
  2343. }
  2344. ast_debug(3, "...Linking account %s\n", name);
  2345. AST_LIST_LOCK(&minivm_accounts);
  2346. AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
  2347. AST_LIST_UNLOCK(&minivm_accounts);
  2348. global_stats.voicemailaccounts++;
  2349. ast_debug(2, "MVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : "");
  2350. return 0;
  2351. }
  2352. /*! \brief Free Mini Voicemail timezone */
  2353. static void free_zone(struct minivm_zone *z)
  2354. {
  2355. ast_free(z);
  2356. }
  2357. /*! \brief Clear list of timezones */
  2358. static void timezone_destroy_list(void)
  2359. {
  2360. struct minivm_zone *this;
  2361. AST_LIST_LOCK(&minivm_zones);
  2362. while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
  2363. free_zone(this);
  2364. AST_LIST_UNLOCK(&minivm_zones);
  2365. }
  2366. /*! \brief Add time zone to memory list */
  2367. static int timezone_add(const char *zonename, const char *config)
  2368. {
  2369. struct minivm_zone *newzone;
  2370. char *msg_format, *timezone_str;
  2371. newzone = ast_calloc(1, sizeof(*newzone));
  2372. if (newzone == NULL)
  2373. return 0;
  2374. msg_format = ast_strdupa(config);
  2375. timezone_str = strsep(&msg_format, "|");
  2376. if (!msg_format) {
  2377. ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
  2378. ast_free(newzone);
  2379. return 0;
  2380. }
  2381. ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
  2382. ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
  2383. ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
  2384. AST_LIST_LOCK(&minivm_zones);
  2385. AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
  2386. AST_LIST_UNLOCK(&minivm_zones);
  2387. global_stats.timezones++;
  2388. return 0;
  2389. }
  2390. /*! \brief Read message template from file */
  2391. static char *message_template_parse_filebody(const char *filename) {
  2392. char buf[BUFSIZ * 6];
  2393. char readbuf[BUFSIZ];
  2394. char filenamebuf[BUFSIZ];
  2395. char *writepos;
  2396. char *messagebody;
  2397. FILE *fi;
  2398. int lines = 0;
  2399. if (ast_strlen_zero(filename))
  2400. return NULL;
  2401. if (*filename == '/')
  2402. ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
  2403. else
  2404. snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
  2405. if (!(fi = fopen(filenamebuf, "r"))) {
  2406. ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
  2407. return NULL;
  2408. }
  2409. writepos = buf;
  2410. while (fgets(readbuf, sizeof(readbuf), fi)) {
  2411. lines ++;
  2412. if (writepos != buf) {
  2413. *writepos = '\n'; /* Replace EOL with new line */
  2414. writepos++;
  2415. }
  2416. ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
  2417. writepos += strlen(readbuf) - 1;
  2418. }
  2419. fclose(fi);
  2420. messagebody = ast_calloc(1, strlen(buf + 1));
  2421. ast_copy_string(messagebody, buf, strlen(buf) + 1);
  2422. ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
  2423. ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
  2424. return messagebody;
  2425. }
  2426. /*! \brief Parse emailbody template from configuration file */
  2427. static char *message_template_parse_emailbody(const char *configuration)
  2428. {
  2429. char *tmpread, *tmpwrite;
  2430. char *emailbody = ast_strdup(configuration);
  2431. /* substitute strings \t and \n into the apropriate characters */
  2432. tmpread = tmpwrite = emailbody;
  2433. while ((tmpwrite = strchr(tmpread,'\\'))) {
  2434. int len = strlen("\n");
  2435. switch (tmpwrite[1]) {
  2436. case 'n':
  2437. memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
  2438. strncpy(tmpwrite, "\n", len);
  2439. break;
  2440. case 't':
  2441. memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
  2442. strncpy(tmpwrite, "\t", len);
  2443. break;
  2444. default:
  2445. ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
  2446. }
  2447. tmpread = tmpwrite + len;
  2448. }
  2449. return emailbody;
  2450. }
  2451. /*! \brief Apply general configuration options */
  2452. static int apply_general_options(struct ast_variable *var)
  2453. {
  2454. int error = 0;
  2455. while (var) {
  2456. /* Mail command */
  2457. if (!strcmp(var->name, "mailcmd")) {
  2458. ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
  2459. } else if (!strcmp(var->name, "maxgreet")) {
  2460. global_maxgreet = atoi(var->value);
  2461. } else if (!strcmp(var->name, "maxsilence")) {
  2462. global_maxsilence = atoi(var->value);
  2463. if (global_maxsilence > 0)
  2464. global_maxsilence *= 1000;
  2465. } else if (!strcmp(var->name, "logfile")) {
  2466. if (!ast_strlen_zero(var->value) ) {
  2467. if(*(var->value) == '/')
  2468. ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
  2469. else
  2470. snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
  2471. }
  2472. } else if (!strcmp(var->name, "externnotify")) {
  2473. /* External voicemail notify application */
  2474. ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
  2475. } else if (!strcmp(var->name, "silencetreshold")) {
  2476. /* Silence treshold */
  2477. global_silencethreshold = atoi(var->value);
  2478. } else if (!strcmp(var->name, "maxmessage")) {
  2479. int x;
  2480. if (sscanf(var->value, "%30d", &x) == 1) {
  2481. global_vmmaxmessage = x;
  2482. } else {
  2483. error ++;
  2484. ast_log(LOG_WARNING, "Invalid max message time length\n");
  2485. }
  2486. } else if (!strcmp(var->name, "minmessage")) {
  2487. int x;
  2488. if (sscanf(var->value, "%30d", &x) == 1) {
  2489. global_vmminmessage = x;
  2490. if (global_maxsilence <= global_vmminmessage)
  2491. ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
  2492. } else {
  2493. error ++;
  2494. ast_log(LOG_WARNING, "Invalid min message time length\n");
  2495. }
  2496. } else if (!strcmp(var->name, "format")) {
  2497. ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
  2498. } else if (!strcmp(var->name, "review")) {
  2499. ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
  2500. } else if (!strcmp(var->name, "operator")) {
  2501. ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
  2502. }
  2503. var = var->next;
  2504. }
  2505. return error;
  2506. }
  2507. /*! \brief Load minivoicemail configuration */
  2508. static int load_config(int reload)
  2509. {
  2510. struct ast_config *cfg;
  2511. struct ast_variable *var;
  2512. char *cat;
  2513. const char *chanvar;
  2514. int error = 0;
  2515. struct minivm_template *template;
  2516. struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  2517. cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
  2518. if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
  2519. return 0;
  2520. } else if (cfg == CONFIG_STATUS_FILEINVALID) {
  2521. ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
  2522. return 0;
  2523. }
  2524. ast_mutex_lock(&minivmlock);
  2525. /* Destroy lists to reconfigure */
  2526. message_destroy_list(); /* Destroy list of voicemail message templates */
  2527. timezone_destroy_list(); /* Destroy list of timezones */
  2528. vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
  2529. ast_debug(2, "Destroyed memory objects...\n");
  2530. /* First, set some default settings */
  2531. global_externnotify[0] = '\0';
  2532. global_logfile[0] = '\0';
  2533. global_vmmaxmessage = 2000;
  2534. global_maxgreet = 2000;
  2535. global_vmminmessage = 0;
  2536. strcpy(global_mailcmd, SENDMAIL);
  2537. global_maxsilence = 0;
  2538. global_saydurationminfo = 2;
  2539. ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
  2540. ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
  2541. ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
  2542. /* Reset statistics */
  2543. memset(&global_stats, 0, sizeof(global_stats));
  2544. global_stats.reset = ast_tvnow();
  2545. global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
  2546. /* Make sure we could load configuration file */
  2547. if (!cfg) {
  2548. ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
  2549. ast_mutex_unlock(&minivmlock);
  2550. return 0;
  2551. }
  2552. ast_debug(2, "Loaded configuration file, now parsing\n");
  2553. /* General settings */
  2554. cat = ast_category_browse(cfg, NULL);
  2555. while (cat) {
  2556. ast_debug(3, "Found configuration section [%s]\n", cat);
  2557. if (!strcasecmp(cat, "general")) {
  2558. /* Nothing right now */
  2559. error += apply_general_options(ast_variable_browse(cfg, cat));
  2560. } else if (!strncasecmp(cat, "template-", 9)) {
  2561. /* Template */
  2562. char *name = cat + 9;
  2563. /* Now build and link template to list */
  2564. error += message_template_build(name, ast_variable_browse(cfg, cat));
  2565. } else {
  2566. var = ast_variable_browse(cfg, cat);
  2567. if (!strcasecmp(cat, "zonemessages")) {
  2568. /* Timezones in this context */
  2569. while (var) {
  2570. timezone_add(var->name, var->value);
  2571. var = var->next;
  2572. }
  2573. } else {
  2574. /* Create mailbox from this */
  2575. error += create_vmaccount(cat, var, FALSE);
  2576. }
  2577. }
  2578. /* Find next section in configuration file */
  2579. cat = ast_category_browse(cfg, cat);
  2580. }
  2581. /* Configure the default email template */
  2582. message_template_build("email-default", NULL);
  2583. template = message_template_find("email-default");
  2584. /* Load date format config for voicemail mail */
  2585. if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
  2586. ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
  2587. if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
  2588. ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
  2589. if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
  2590. ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
  2591. if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
  2592. ast_copy_string(template->charset, chanvar, sizeof(template->charset));
  2593. if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
  2594. ast_copy_string(template->subject, chanvar, sizeof(template->subject));
  2595. if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
  2596. template->body = message_template_parse_emailbody(chanvar);
  2597. template->attachment = TRUE;
  2598. message_template_build("pager-default", NULL);
  2599. template = message_template_find("pager-default");
  2600. if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
  2601. ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
  2602. if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
  2603. ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
  2604. if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
  2605. ast_copy_string(template->charset, chanvar, sizeof(template->charset));
  2606. if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
  2607. ast_copy_string(template->subject, chanvar,sizeof(template->subject));
  2608. if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
  2609. template->body = message_template_parse_emailbody(chanvar);
  2610. template->attachment = FALSE;
  2611. if (error)
  2612. ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
  2613. ast_mutex_unlock(&minivmlock);
  2614. ast_config_destroy(cfg);
  2615. /* Close log file if it's open and disabled */
  2616. if(minivmlogfile)
  2617. fclose(minivmlogfile);
  2618. /* Open log file if it's enabled */
  2619. if(!ast_strlen_zero(global_logfile)) {
  2620. minivmlogfile = fopen(global_logfile, "a");
  2621. if(!minivmlogfile)
  2622. ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
  2623. if (minivmlogfile)
  2624. ast_debug(3, "Opened log file %s \n", global_logfile);
  2625. }
  2626. return 0;
  2627. }
  2628. /*! \brief CLI routine for listing templates */
  2629. static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  2630. {
  2631. struct minivm_template *this;
  2632. #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
  2633. int count = 0;
  2634. switch (cmd) {
  2635. case CLI_INIT:
  2636. e->command = "minivm list templates";
  2637. e->usage =
  2638. "Usage: minivm list templates\n"
  2639. " Lists message templates for e-mail, paging and IM\n";
  2640. return NULL;
  2641. case CLI_GENERATE:
  2642. return NULL;
  2643. }
  2644. if (a->argc > 3)
  2645. return CLI_SHOWUSAGE;
  2646. AST_LIST_LOCK(&message_templates);
  2647. if (AST_LIST_EMPTY(&message_templates)) {
  2648. ast_cli(a->fd, "There are no message templates defined\n");
  2649. AST_LIST_UNLOCK(&message_templates);
  2650. return CLI_FAILURE;
  2651. }
  2652. ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
  2653. ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
  2654. AST_LIST_TRAVERSE(&message_templates, this, list) {
  2655. ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
  2656. this->charset ? this->charset : "-",
  2657. this->locale ? this->locale : "-",
  2658. this->attachment ? "Yes" : "No",
  2659. this->subject ? this->subject : "-");
  2660. count++;
  2661. }
  2662. AST_LIST_UNLOCK(&message_templates);
  2663. ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
  2664. return CLI_SUCCESS;
  2665. }
  2666. static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
  2667. {
  2668. int which = 0;
  2669. int wordlen;
  2670. struct minivm_account *vmu;
  2671. const char *domain = "";
  2672. /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
  2673. if (pos > 4)
  2674. return NULL;
  2675. if (pos == 3)
  2676. return (state == 0) ? ast_strdup("for") : NULL;
  2677. wordlen = strlen(word);
  2678. AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
  2679. if (!strncasecmp(word, vmu->domain, wordlen)) {
  2680. if (domain && strcmp(domain, vmu->domain) && ++which > state)
  2681. return ast_strdup(vmu->domain);
  2682. /* ignore repeated domains ? */
  2683. domain = vmu->domain;
  2684. }
  2685. }
  2686. return NULL;
  2687. }
  2688. /*! \brief CLI command to list voicemail accounts */
  2689. static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  2690. {
  2691. struct minivm_account *vmu;
  2692. #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
  2693. int count = 0;
  2694. switch (cmd) {
  2695. case CLI_INIT:
  2696. e->command = "minivm list accounts";
  2697. e->usage =
  2698. "Usage: minivm list accounts\n"
  2699. " Lists all mailboxes currently set up\n";
  2700. return NULL;
  2701. case CLI_GENERATE:
  2702. return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
  2703. }
  2704. if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
  2705. return CLI_SHOWUSAGE;
  2706. if ((a->argc == 5) && strcmp(a->argv[3],"for"))
  2707. return CLI_SHOWUSAGE;
  2708. AST_LIST_LOCK(&minivm_accounts);
  2709. if (AST_LIST_EMPTY(&minivm_accounts)) {
  2710. ast_cli(a->fd, "There are no voicemail users currently defined\n");
  2711. AST_LIST_UNLOCK(&minivm_accounts);
  2712. return CLI_FAILURE;
  2713. }
  2714. ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
  2715. ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
  2716. AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
  2717. char tmp[256] = "";
  2718. if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
  2719. count++;
  2720. snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
  2721. ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
  2722. vmu->ptemplate ? vmu->ptemplate : "-",
  2723. vmu->zonetag ? vmu->zonetag : "-",
  2724. vmu->attachfmt ? vmu->attachfmt : "-",
  2725. vmu->fullname);
  2726. }
  2727. }
  2728. AST_LIST_UNLOCK(&minivm_accounts);
  2729. ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
  2730. return CLI_SUCCESS;
  2731. }
  2732. /*! \brief Show a list of voicemail zones in the CLI */
  2733. static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  2734. {
  2735. struct minivm_zone *zone;
  2736. #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
  2737. char *res = CLI_SUCCESS;
  2738. switch (cmd) {
  2739. case CLI_INIT:
  2740. e->command = "minivm list zones";
  2741. e->usage =
  2742. "Usage: minivm list zones\n"
  2743. " Lists zone message formats\n";
  2744. return NULL;
  2745. case CLI_GENERATE:
  2746. return NULL;
  2747. }
  2748. if (a->argc != e->args)
  2749. return CLI_SHOWUSAGE;
  2750. AST_LIST_LOCK(&minivm_zones);
  2751. if (!AST_LIST_EMPTY(&minivm_zones)) {
  2752. ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
  2753. ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
  2754. AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
  2755. ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
  2756. }
  2757. } else {
  2758. ast_cli(a->fd, "There are no voicemail zones currently defined\n");
  2759. res = CLI_FAILURE;
  2760. }
  2761. AST_LIST_UNLOCK(&minivm_zones);
  2762. return res;
  2763. }
  2764. /*! \brief CLI Show settings */
  2765. static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  2766. {
  2767. switch (cmd) {
  2768. case CLI_INIT:
  2769. e->command = "minivm show settings";
  2770. e->usage =
  2771. "Usage: minivm show settings\n"
  2772. " Display Mini-Voicemail general settings\n";
  2773. return NULL;
  2774. case CLI_GENERATE:
  2775. return NULL;
  2776. }
  2777. ast_cli(a->fd, "* Mini-Voicemail general settings\n");
  2778. ast_cli(a->fd, " -------------------------------\n");
  2779. ast_cli(a->fd, "\n");
  2780. ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
  2781. ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
  2782. ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
  2783. ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
  2784. ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
  2785. ast_cli(a->fd, " Default format: %s\n", default_vmformat);
  2786. ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
  2787. ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
  2788. ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
  2789. ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
  2790. ast_cli(a->fd, "\n");
  2791. return CLI_SUCCESS;
  2792. }
  2793. /*! \brief Show stats */
  2794. static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  2795. {
  2796. struct ast_tm timebuf;
  2797. char buf[BUFSIZ];
  2798. switch (cmd) {
  2799. case CLI_INIT:
  2800. e->command = "minivm show stats";
  2801. e->usage =
  2802. "Usage: minivm show stats\n"
  2803. " Display Mini-Voicemail counters\n";
  2804. return NULL;
  2805. case CLI_GENERATE:
  2806. return NULL;
  2807. }
  2808. ast_cli(a->fd, "* Mini-Voicemail statistics\n");
  2809. ast_cli(a->fd, " -------------------------\n");
  2810. ast_cli(a->fd, "\n");
  2811. ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
  2812. ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
  2813. ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
  2814. if (global_stats.receivedmessages == 0) {
  2815. ast_cli(a->fd, " Received messages since last reset: <none>\n");
  2816. } else {
  2817. ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
  2818. ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
  2819. ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
  2820. ast_cli(a->fd, " Last received voicemail: %s\n", buf);
  2821. }
  2822. ast_localtime(&global_stats.reset, &timebuf, NULL);
  2823. ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
  2824. ast_cli(a->fd, " Last reset: %s\n", buf);
  2825. ast_cli(a->fd, "\n");
  2826. return CLI_SUCCESS;
  2827. }
  2828. /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
  2829. static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
  2830. {
  2831. struct minivm_account *vmu;
  2832. char *username, *domain, *colname;
  2833. username = ast_strdupa(data);
  2834. if ((colname = strchr(username, ':'))) {
  2835. *colname = '\0';
  2836. colname++;
  2837. } else {
  2838. colname = "path";
  2839. }
  2840. if ((domain = strchr(username, '@'))) {
  2841. *domain = '\0';
  2842. domain++;
  2843. }
  2844. if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
  2845. ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
  2846. return 0;
  2847. }
  2848. if (!(vmu = find_account(domain, username, TRUE)))
  2849. return 0;
  2850. if (!strcasecmp(colname, "hasaccount")) {
  2851. ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
  2852. } else if (!strcasecmp(colname, "fullname")) {
  2853. ast_copy_string(buf, vmu->fullname, len);
  2854. } else if (!strcasecmp(colname, "email")) {
  2855. if (!ast_strlen_zero(vmu->email))
  2856. ast_copy_string(buf, vmu->email, len);
  2857. else
  2858. snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
  2859. } else if (!strcasecmp(colname, "pager")) {
  2860. ast_copy_string(buf, vmu->pager, len);
  2861. } else if (!strcasecmp(colname, "etemplate")) {
  2862. if (!ast_strlen_zero(vmu->etemplate))
  2863. ast_copy_string(buf, vmu->etemplate, len);
  2864. else
  2865. ast_copy_string(buf, "email-default", len);
  2866. } else if (!strcasecmp(colname, "language")) {
  2867. ast_copy_string(buf, vmu->language, len);
  2868. } else if (!strcasecmp(colname, "timezone")) {
  2869. ast_copy_string(buf, vmu->zonetag, len);
  2870. } else if (!strcasecmp(colname, "ptemplate")) {
  2871. if (!ast_strlen_zero(vmu->ptemplate))
  2872. ast_copy_string(buf, vmu->ptemplate, len);
  2873. else
  2874. ast_copy_string(buf, "email-default", len);
  2875. } else if (!strcasecmp(colname, "accountcode")) {
  2876. ast_copy_string(buf, vmu->accountcode, len);
  2877. } else if (!strcasecmp(colname, "pincode")) {
  2878. ast_copy_string(buf, vmu->pincode, len);
  2879. } else if (!strcasecmp(colname, "path")) {
  2880. check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
  2881. } else { /* Look in channel variables */
  2882. struct ast_variable *var;
  2883. for (var = vmu->chanvars ; var ; var = var->next)
  2884. if (!strcmp(var->name, colname)) {
  2885. ast_copy_string(buf, var->value, len);
  2886. break;
  2887. }
  2888. }
  2889. if(ast_test_flag(vmu, MVM_ALLOCED))
  2890. free_user(vmu);
  2891. return 0;
  2892. }
  2893. /*! \brief lock directory
  2894. only return failure if ast_lock_path returns 'timeout',
  2895. not if the path does not exist or any other reason
  2896. */
  2897. static int vm_lock_path(const char *path)
  2898. {
  2899. switch (ast_lock_path(path)) {
  2900. case AST_LOCK_TIMEOUT:
  2901. return -1;
  2902. default:
  2903. return 0;
  2904. }
  2905. }
  2906. /*! \brief Access counter file, lock directory, read and possibly write it again changed
  2907. \param directory Directory to crate file in
  2908. \param countername filename
  2909. \param value If set to zero, we only read the variable
  2910. \param operand 0 to read, 1 to set new value, 2 to change
  2911. \return -1 on error, otherwise counter value
  2912. */
  2913. static int access_counter_file(char *directory, char *countername, int value, int operand)
  2914. {
  2915. char filename[BUFSIZ];
  2916. char readbuf[BUFSIZ];
  2917. FILE *counterfile;
  2918. int old = 0, counter = 0;
  2919. /* Lock directory */
  2920. if (vm_lock_path(directory)) {
  2921. return -1; /* Could not lock directory */
  2922. }
  2923. snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
  2924. if (operand != 1) {
  2925. counterfile = fopen(filename, "r");
  2926. if (counterfile) {
  2927. if(fgets(readbuf, sizeof(readbuf), counterfile)) {
  2928. ast_debug(3, "Read this string from counter file: %s\n", readbuf);
  2929. old = counter = atoi(readbuf);
  2930. }
  2931. fclose(counterfile);
  2932. }
  2933. }
  2934. switch (operand) {
  2935. case 0: /* Read only */
  2936. ast_unlock_path(directory);
  2937. ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
  2938. return counter;
  2939. break;
  2940. case 1: /* Set new value */
  2941. counter = value;
  2942. break;
  2943. case 2: /* Change value */
  2944. counter += value;
  2945. if (counter < 0) /* Don't allow counters to fall below zero */
  2946. counter = 0;
  2947. break;
  2948. }
  2949. /* Now, write the new value to the file */
  2950. counterfile = fopen(filename, "w");
  2951. if (!counterfile) {
  2952. ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
  2953. ast_unlock_path(directory);
  2954. return -1; /* Could not open file for writing */
  2955. }
  2956. fprintf(counterfile, "%d\n\n", counter);
  2957. fclose(counterfile);
  2958. ast_unlock_path(directory);
  2959. ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
  2960. return counter;
  2961. }
  2962. /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
  2963. static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
  2964. {
  2965. char *username, *domain, *countername;
  2966. char userpath[BUFSIZ];
  2967. int res;
  2968. *buf = '\0';
  2969. username = ast_strdupa(data);
  2970. if ((countername = strchr(username, ':'))) {
  2971. *countername = '\0';
  2972. countername++;
  2973. }
  2974. if ((domain = strchr(username, '@'))) {
  2975. *domain = '\0';
  2976. domain++;
  2977. }
  2978. /* If we have neither username nor domain now, let's give up */
  2979. if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
  2980. ast_log(LOG_ERROR, "No account given\n");
  2981. return -1;
  2982. }
  2983. if (ast_strlen_zero(countername)) {
  2984. ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
  2985. return -1;
  2986. }
  2987. /* We only have a domain, no username */
  2988. if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
  2989. domain = username;
  2990. username = NULL;
  2991. }
  2992. /* If we can't find account or if the account is temporary, return. */
  2993. if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
  2994. ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
  2995. return 0;
  2996. }
  2997. create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
  2998. /* We have the path, now read the counter file */
  2999. res = access_counter_file(userpath, countername, 0, 0);
  3000. if (res >= 0)
  3001. snprintf(buf, len, "%d", res);
  3002. return 0;
  3003. }
  3004. /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
  3005. static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
  3006. {
  3007. char *username, *domain, *countername, *operand;
  3008. char userpath[BUFSIZ];
  3009. int change = 0;
  3010. int operation = 0;
  3011. if(!value)
  3012. return -1;
  3013. change = atoi(value);
  3014. username = ast_strdupa(data);
  3015. if ((countername = strchr(username, ':'))) {
  3016. *countername = '\0';
  3017. countername++;
  3018. }
  3019. if ((operand = strchr(countername, ':'))) {
  3020. *operand = '\0';
  3021. operand++;
  3022. }
  3023. if ((domain = strchr(username, '@'))) {
  3024. *domain = '\0';
  3025. domain++;
  3026. }
  3027. /* If we have neither username nor domain now, let's give up */
  3028. if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
  3029. ast_log(LOG_ERROR, "No account given\n");
  3030. return -1;
  3031. }
  3032. /* We only have a domain, no username */
  3033. if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
  3034. domain = username;
  3035. username = NULL;
  3036. }
  3037. if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
  3038. ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
  3039. return -1;
  3040. }
  3041. /* If we can't find account or if the account is temporary, return. */
  3042. if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
  3043. ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
  3044. return 0;
  3045. }
  3046. create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
  3047. /* Now, find out our operator */
  3048. if (*operand == 'i') /* Increment */
  3049. operation = 2;
  3050. else if (*operand == 'd') {
  3051. change = change * -1;
  3052. operation = 2;
  3053. } else if (*operand == 's')
  3054. operation = 1;
  3055. else {
  3056. ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
  3057. return -1;
  3058. }
  3059. /* We have the path, now read the counter file */
  3060. access_counter_file(userpath, countername, change, operation);
  3061. return 0;
  3062. }
  3063. /*! \brief CLI commands for Mini-voicemail */
  3064. static struct ast_cli_entry cli_minivm[] = {
  3065. AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
  3066. AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
  3067. AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"),
  3068. AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
  3069. AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
  3070. AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
  3071. };
  3072. static struct ast_custom_function minivm_counter_function = {
  3073. .name = "MINIVMCOUNTER",
  3074. .read = minivm_counter_func_read,
  3075. .write = minivm_counter_func_write,
  3076. };
  3077. static struct ast_custom_function minivm_account_function = {
  3078. .name = "MINIVMACCOUNT",
  3079. .read = minivm_account_func_read,
  3080. };
  3081. /*! \brief Load mini voicemail module */
  3082. static int load_module(void)
  3083. {
  3084. int res;
  3085. res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
  3086. res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
  3087. res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
  3088. res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
  3089. res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
  3090. res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
  3091. ast_custom_function_register(&minivm_account_function);
  3092. ast_custom_function_register(&minivm_counter_function);
  3093. if (res)
  3094. return(res);
  3095. if ((res = load_config(0)))
  3096. return(res);
  3097. ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
  3098. /* compute the location of the voicemail spool directory */
  3099. snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
  3100. return res;
  3101. }
  3102. /*! \brief Reload mini voicemail module */
  3103. static int reload(void)
  3104. {
  3105. return(load_config(1));
  3106. }
  3107. /*! \brief Reload cofiguration */
  3108. static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  3109. {
  3110. switch (cmd) {
  3111. case CLI_INIT:
  3112. e->command = "minivm reload";
  3113. e->usage =
  3114. "Usage: minivm reload\n"
  3115. " Reload mini-voicemail configuration and reset statistics\n";
  3116. return NULL;
  3117. case CLI_GENERATE:
  3118. return NULL;
  3119. }
  3120. reload();
  3121. ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
  3122. return CLI_SUCCESS;
  3123. }
  3124. /*! \brief Unload mini voicemail module */
  3125. static int unload_module(void)
  3126. {
  3127. int res;
  3128. res = ast_unregister_application(app_minivm_record);
  3129. res |= ast_unregister_application(app_minivm_greet);
  3130. res |= ast_unregister_application(app_minivm_notify);
  3131. res |= ast_unregister_application(app_minivm_delete);
  3132. res |= ast_unregister_application(app_minivm_accmess);
  3133. res |= ast_unregister_application(app_minivm_mwi);
  3134. ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
  3135. ast_custom_function_unregister(&minivm_account_function);
  3136. ast_custom_function_unregister(&minivm_counter_function);
  3137. message_destroy_list(); /* Destroy list of voicemail message templates */
  3138. timezone_destroy_list(); /* Destroy list of timezones */
  3139. vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
  3140. return res;
  3141. }
  3142. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
  3143. .support_level = AST_MODULE_SUPPORT_EXTENDED,
  3144. .load = load_module,
  3145. .unload = unload_module,
  3146. .reload = reload,
  3147. );