76_MSGMail.pm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. ########################################################
  2. # $Id: 76_MSGMail.pm 12723 2016-12-07 16:55:33Z gandy92 $
  3. ########################################################
  4. #
  5. # History:
  6. #
  7. # 2016-01-27: Add MSGMail_send() to be used from perl
  8. # Use Blocking_Call to deliver email
  9. # 2015-05-19: Add MSGMail_Attr()
  10. # 2015-05-15: Add attribute mailtype as suggested by Roger (forum #37206)
  11. # 2015-05-11: Improve error logging to assist problem solving
  12. # 2015-05-09: Assimilate mail related code from 75_MSG
  13. # 2015-05-06: Tidy up code for restructuring
  14. # 2015-05-05: Remove dependency on Switch
  15. # 2012 : Created by rbente
  16. #
  17. ##
  18. #
  19. # Further reading:
  20. #
  21. # http://search.cpan.org/~yves/MIME-Lite-3.01/lib/MIME/Lite.pm
  22. #
  23. #
  24. package main;
  25. use strict;
  26. use warnings;
  27. use MIME::Lite;
  28. use Net::SMTP; # libnet-3.06 has SSL included, so we need to check the version
  29. require 'Blocking.pm';
  30. my %sets = (
  31. "add" => "MSGMail",
  32. "clear" => "MSGMail",
  33. "list" => "MSGMail",
  34. "send" => "MSGMail"
  35. );
  36. my $MSGMail_SSL = 0;
  37. my $MSGMail_SMTP = 0;
  38. ##############################################
  39. # Initialize Function
  40. # Attributes are:
  41. # authfile the name of the file which contains userid and password
  42. # smtphost the smtp server hostname
  43. # smtpport the port of the smtp host
  44. # subject subject of the email
  45. # from from mailaddress (sender)
  46. # to to mailaddress (receipent)
  47. # cc carbon copy address(es) (delimiter is comma)
  48. # CR 0 = no CR added to the end of the line
  49. # 1 = CR added to the end of the line
  50. ##############################################
  51. sub MSGMail_Initialize($)
  52. {
  53. my ($hash) = @_;
  54. $hash->{SetFn} = "MSGMail_Set";
  55. $hash->{DefFn} = "MSGMail_Define";
  56. $hash->{UndefFn} = "MSGMail_Undef";
  57. $hash->{AttrFn} = "MSGMail_Attr";
  58. $hash->{AttrList} =
  59. "loglevel:0,1,2,3,4,5,6" . " authfile smtphost smtpport" . " subject mailtype:plain,html from to cc CR:0,1";
  60. my $name = "MSGMail";
  61. # check version of libnet - if < 3.00, try to load Net::SMTP::SSL
  62. $MSGMail_SMTP = $Net::SMTP::VERSION;
  63. $MSGMail_SMTP =~ s/[^0-9.].*$//;
  64. if ($MSGMail_SMTP >= 3.00)
  65. {
  66. $MSGMail_SSL = 1;
  67. }
  68. else
  69. {
  70. eval "use Net::SMTP::SSL";
  71. if ($@)
  72. {
  73. Log 0, $@ if ($@);
  74. $MSGMail_SSL = 0;
  75. }
  76. else
  77. {
  78. $MSGMail_SSL = 1;
  79. }
  80. }
  81. Log 0, "$name: SSL is "
  82. . (
  83. ($MSGMail_SSL)
  84. ? ("available, provided by Net::SMTP" . (($MSGMail_SMTP < 3.00) ? "::SSL" : ""))
  85. : "not available"
  86. );
  87. }
  88. ##############################################
  89. # Define Function
  90. # set the counter to 0
  91. ##############################################
  92. sub MSGMail_Define($$)
  93. {
  94. my ($hash, $def) = @_;
  95. my @a = split("[ \t][ \t]*", $def);
  96. my $errmsg = "wrong syntax: define <name> MSGMail from to smtphost authfile";
  97. my $name = $hash->{NAME};
  98. return $errmsg if (@a != 6);
  99. # set all the Attributes
  100. $attr{$name}{from} = $a[2];
  101. $attr{$name}{to} = $a[3];
  102. $attr{$name}{smtphost} = $a[4];
  103. $attr{$name}{authfile} = $a[5];
  104. $attr{$name}{mailtype} = "plain";
  105. $attr{$name}{subject} = "FHEM ";
  106. $attr{$name}{CR} = "1";
  107. $hash->{STATE} = "ready";
  108. $hash->{TYPE} = "MSGMail";
  109. $hash->{READINGS}{msgcount}{TIME} = TimeNow();
  110. $hash->{READINGS}{msgcount}{VAL} = 0;
  111. return undef;
  112. }
  113. ##############################################
  114. # Undefine Function
  115. # flush all lines of data
  116. ##############################################
  117. sub MSGMail_Undef($$)
  118. {
  119. my ($hash, $name) = @_;
  120. my $i;
  121. # flush the data
  122. for ($i = 0 ; $i < ReadingsVal($name, "msgcount", 0) ; $i++)
  123. {
  124. $data{$name}{$i} = "";
  125. }
  126. delete($modules{MSGMail}{defptr}{ $hash->{CODE} }) if ($hash && $hash->{CODE});
  127. return undef;
  128. }
  129. ##############################################
  130. # Attr Function
  131. #
  132. # Check sanity of attribute values; no need to actually set attributes, this is done by FHEM
  133. # return undef if OK, otherwise return error message
  134. #
  135. # authfile smtphost smtpport subject mailtype:plain,html from to cc
  136. ##############################################
  137. sub MSGMail_Attr(@)
  138. {
  139. my ($cmd, $name, $attrName, $attrVal) = @_;
  140. my $hash = $defs{$name};
  141. return if ($cmd ne "set");
  142. if ($attrName eq "loglevel") # loglevel:0,1,2,3,4,5,6
  143. {
  144. return if ($attrVal =~ m/^[0-6]$/);
  145. return "Valid values for loglevel are 0,1,2,3,4,5,6";
  146. }
  147. elsif ($attrName eq "mailtype") # mailtype:plain,html
  148. {
  149. return if ($attrVal =~ m/^(plain|html)$/);
  150. return "Valid values for mailtype are plain,html";
  151. }
  152. elsif ($attrName eq "CR") # CR:0,1
  153. {
  154. return if ($attrVal =~ m/^[01]$/);
  155. return "Valid values for mailtype are 0,1";
  156. }
  157. return undef;
  158. }
  159. ##############################################
  160. # Set Function
  161. # all the data are stored in the global array @data
  162. # as counter we use a READING named msgcount
  163. ##############################################
  164. sub MSGMail_Set($@)
  165. {
  166. my ($hash, @a) = @_;
  167. return "Unknown argument $a[1], choose one of -> " . join(" ", sort keys %sets)
  168. if (!defined($sets{ $a[1] }));
  169. my $name = shift @a;
  170. return "no set value specified" if (int(@a) < 1);
  171. # return "Unknown argument ?" if($a[0] eq "?");
  172. my $v = join(" ", @a);
  173. # we like to add another line of data
  174. if ($a[0] eq "add")
  175. {
  176. # split the line in command and data
  177. my $mx = shift @a;
  178. my $my = join(" ", @a);
  179. # check if we like to have and CR at the end of the line
  180. if (AttrVal($name, "CR", "0") eq "1")
  181. {
  182. $my = $my . "\n";
  183. }
  184. # get the highest number of lines, stored the line in @data and increase
  185. # the counter, at the end set the status
  186. my $count = $hash->{READINGS}{msgcount}{VAL};
  187. $data{$name}{$count} = $my;
  188. $hash->{READINGS}{msgcount}{TIME} = TimeNow();
  189. $hash->{READINGS}{msgcount}{VAL} = $count + 1;
  190. $hash->{STATE} = "addmsg";
  191. }
  192. # we like to clear our buffer, first clear all lines of @data
  193. # and then set the counter to 0 and the status to clear
  194. elsif ($a[0] eq "clear")
  195. {
  196. my $i;
  197. for ($i = 0 ; $i < ReadingsVal($name, "msgcount", 0) ; $i++)
  198. {
  199. $data{$name}{$i} = "";
  200. }
  201. $hash->{READINGS}{msgcount}{TIME} = TimeNow();
  202. $hash->{READINGS}{msgcount}{VAL} = 0;
  203. $hash->{STATE} = "clear";
  204. }
  205. # we like to see the buffer
  206. elsif ($a[0] eq "list")
  207. {
  208. my $i;
  209. my $mess = "---- Lines of data for $name ----\n";
  210. for ($i = 0 ; $i < ReadingsVal($name, "msgcount", 0) ; $i++)
  211. {
  212. $mess .= $data{$name}{$i};
  213. }
  214. return "$mess---- End of data for $name ----";
  215. }
  216. elsif ($a[0] eq "send")
  217. {
  218. my $mess = "";
  219. for (my $i = 0 ; $i < ReadingsVal($name, "msgcount", 0) ; $i++)
  220. {
  221. $mess .= $data{$name}{$i};
  222. }
  223. return MSGMail_send($name, $mess, {});
  224. } ###> END MSGMail
  225. Log GetLogLevel($name, 2), "messenger set $name $v";
  226. # set stats
  227. # $hash->{CHANGED}[0] = $v;
  228. $hash->{READINGS}{state}{TIME} = TimeNow();
  229. $hash->{READINGS}{state}{VAL} = $v;
  230. return undef;
  231. }
  232. my %MSGMail_params = (
  233. 'from' => {'attr'=>'from', 'default'=>'', 'required'=>1, 'set'=>1},
  234. 'to' => {'attr'=>'to', 'default'=>'', 'required'=>1, 'set'=>1},
  235. 'subject' => {'attr'=>'subject', 'default'=>'', 'required'=>1, 'set'=>1},
  236. 'cc' => {'attr'=>'cc', 'default'=>'', 'required'=>0, 'set'=>1},
  237. 'mtype' => {'attr'=>'mailtype', 'default'=>'plain', 'required'=>1, 'set'=>1},
  238. 'authfile' => {'attr'=>'authfile', 'default'=>'', 'required'=>1, 'set'=>0},
  239. 'smtphost' => {'attr'=>'smtphost', 'default'=>'', 'required'=>1, 'set'=>0},
  240. 'smtpport' => {'attr'=>'smtpport', 'default'=>'465', 'required'=>1, 'set'=>0},
  241. );
  242. sub MSGMail_send($$;$)
  243. {
  244. my ($name, $msgtext, $extparamref) = @_;
  245. my %params = ();
  246. return "unknown device $name." unless exists ($defs{$name});
  247. foreach my $key (keys %MSGMail_params)
  248. {
  249. $params{$key} = AttrVal($name, $MSGMail_params{$key}->{attr}, $MSGMail_params{$key}->{default});
  250. $params{$key} = $extparamref->{$key} if (exists $extparamref->{$key} && $MSGMail_params{$key}->{set});
  251. Log3 $name, 4, "$name: param $key is now '".$params{$key}."' (attribute '".$MSGMail_params{$key}->{attr}."')";
  252. }
  253. my $mailtype = "text/plain";
  254. $mailtype = "text/".$params{mtype} if ($params{mtype} =~ m/^(plain|html)$/);
  255. $params{mailtype} = $mailtype;
  256. $params{name} = $name;
  257. $params{msgtext} = $msgtext;
  258. Log3 $name, 4, "$name: mailtype is $mailtype";
  259. my @err = ();
  260. foreach my $key (keys(%MSGMail_params))
  261. {
  262. push(@err, $key) if ($MSGMail_params{$key}->{required} && !$params{$key});
  263. }
  264. return "Missing at least one required parameter or attribue: ".join(', ',@err) if ($#err >= 0);
  265. BlockingCall("MSGMail_sendInBackground", \%params);
  266. return "";
  267. }
  268. sub MSGMail_error($$$)
  269. {
  270. my ($name, $msg, $error) = @_;
  271. Log3 $name, 0, "$name: $msg: $error";
  272. return $error;
  273. }
  274. sub MSGMail_sendInBackground($)
  275. {
  276. my ($paref) = @_;
  277. my $authfile = $paref->{authfile};
  278. my $name = $paref->{name};
  279. my $cc = $paref->{cc};
  280. my $from = $paref->{from};
  281. my $mailtype = $paref->{mailtype};
  282. my $smtphost = $paref->{smtphost};
  283. my $subject = $paref->{subject};
  284. my $to = $paref->{to};
  285. my $msgtext = $paref->{msgtext};
  286. open(FHEMAUTHFILE, "<" . $authfile)
  287. || return "Can not open authfile $authfile: $!";
  288. my @auth = <FHEMAUTHFILE>;
  289. close(FHEMAUTHFILE);
  290. chomp(@auth);
  291. # Log 1, "MSG User = <" . @auth[0] . "> Passwort = <" . @auth[1] . ">";
  292. my $mailmsg = MIME::Lite->new(
  293. From => $from,
  294. To => $to,
  295. Subject => $subject,
  296. Type => "$mailtype; charset=UTF-8", #'multipart/mixed', # was 'text/plain'
  297. Data => $msgtext
  298. );
  299. # login to the SMTP Host using SSL and send the message
  300. my $smtp;
  301. my $smtperrmsg = "SMTP Error: ";
  302. #$smtp = Net::SMTP::SSL->new($smtphost, Port => $smtpport)
  303. $smtp = MSGMail_conn($defs{$name})
  304. or return MSGMail_error($name, "Can't connect to host $smtphost",
  305. $smtperrmsg . " Can't connect to host $smtphost");
  306. $smtp->auth($auth[0], $auth[1])
  307. or
  308. return MSGMail_error($name, "Can't authenticate", $smtperrmsg . " Can't authenticate: " . $smtp->message());
  309. $smtp->mail($from)
  310. or return MSGMail_error($name, "Error setting sender '$from'", $smtperrmsg . $smtp->message());
  311. $smtp->to($to) or return MSGMail_error($name, "Error setting receiver '$to'", $smtperrmsg . $smtp->message());
  312. if ($cc ne '')
  313. {
  314. Log3 $name, 1, "$name: CC = $cc";
  315. $smtp->cc($cc)
  316. or return MSGMail_error($name, "Error setting carbon-copy $cc", $smtperrmsg . $smtp->message());
  317. }
  318. $smtp->data() or return MSGMail_error($name, "Error setting data", $smtperrmsg . $smtp->message());
  319. $smtp->datasend($mailmsg->as_string)
  320. or return MSGMail_error($name, "Error sending email", $smtperrmsg . $smtp->message());
  321. $smtp->dataend() or return MSGMail_error($name, "Error ending transaction", $smtperrmsg . $smtp->message());
  322. $smtp->quit() or return MSGMail_error($name, "Error saying good-bye", $smtperrmsg . $smtp->message());
  323. Log3 $name, 1, "$name: successfully sent email w/ subject '$subject'";
  324. }
  325. ##############################################
  326. # Helper Function to connect to mail server
  327. # Returns a smtp connection (see Net:SMTP)
  328. ##############################################
  329. sub MSGMail_conn($)
  330. {
  331. my ($hash) = @_;
  332. my ($name) = $hash->{NAME};
  333. my $smtphost = AttrVal($name, "smtphost", "");
  334. my $smtpport = AttrVal($name, "smtpport", "465"); # 465 is the default port
  335. if ($MSGMail_SSL)
  336. {
  337. if ($MSGMail_SMTP < 3.00)
  338. {
  339. Log3 $name, 3, "$name: try to connect with Net::SMTP::SSL";
  340. return Net::SMTP::SSL->new($smtphost, Port => $smtpport);
  341. }
  342. else
  343. {
  344. Log3 $name, 3, "$name: try to connect with Net::SMTP";
  345. return Net::SMTP->new(Host => $smtphost, Port => $smtpport, SSL => 1);
  346. }
  347. }
  348. Log3 $name, 0, "$name: SSL not available. Connection will fail";
  349. return undef;
  350. }
  351. 1;
  352. =pod
  353. =item device
  354. =item summary sends mail through a SMTP server, optionally with SSL encryption
  355. =begin html
  356. <a name="MSGMail"></a>
  357. <h3>MSGMail</h3>
  358. <ul>
  359. The MSGMail device is used to send mail messages to a recipient by connecting
  360. to a SMTP server. Currently MSGMail supports only servers, that allow SSL secured connections
  361. like Googlemail, GMX, Yahoo or 1und1.
  362. MSGMail requires the perl pacakge <b>MAIL::Lite</b>.
  363. For SSL support, Net::SMTP version 3.06 is required. On systems with an older version of Net::SMTP,
  364. MSGMail requires the package <b>Net::SMTP::SSL</b>.
  365. <br><br>
  366. <a name="MSGMailDefine"></a>
  367. <b>Define</b>
  368. <ul><br>
  369. <code>define &lt;name&gt; MSGMail &lt;from&gt; &lt;to&gt; &lt;smtphost&gt; &lt;authfile&gt;</code><br><br>
  370. Specifies the MSGMail device. At definition the message counter is set to 0.
  371. From, To, SMTPHost and the authfile (see attributes below) need to be defined
  372. at definition time.
  373. </ul>
  374. <br>
  375. Examples:
  376. <ul>
  377. <code>define myMail MSGMail from@address.com to@address.com smtp.provider.host /etc/msgauthfile</code>
  378. </ul><br>
  379. <a name="MSGMailSet"></a>
  380. <b>Set</b><br>
  381. <ul><code>set &lt;name&gt; add|clear|list|send [text]</code><br>
  382. Set is used to manipulate the message buffer of the device. The message
  383. buffer is an array of lines of data, stored serial based on the incoming
  384. time into the buffer. Lines of data inside the buffer could not be deleted
  385. anymore, except of flashing the whole buffer.<br>
  386. <ul><b>add</b><br> to add lines of data to the message buffer. All data behind
  387. "add" will be interpreted as text message. To add a carriage return to the data,
  388. please use the CR attribute.
  389. </ul>
  390. <ul><b>clear</b><br> to flush the message buffer and set the line counter to 0.
  391. All the lines of data are deleted and the buffer is flushed.</ul>
  392. <ul><b>list</b><br> to list the message buffer.<br></ul>
  393. <ul><b>send</b><br> to send the message buffer.<br></ul>
  394. <br>
  395. Examples:
  396. <ul>
  397. <code>set myMail add Dies ist Textzeile 1</code><br>
  398. <code>set myMail add Dies ist Textzeile 2</code><br>
  399. <code>set myMail clear</code><br><br>
  400. Full working example to send two lines of data to a recipent:<br>
  401. <code>define myMail MSGMail donald.duck@entenhausen.com dagobert.duck@duck-banking.com smtp.entenhausen.net /etc/fhem/msgmailauth</code><br>
  402. <code>attr myMail smtpport 9999</code><br>
  403. <code>attr myMail subject i need more money</code><br>
  404. <code>attr myMail CR 0</code><br>
  405. <code>set myMail add Please send me </code><br>
  406. <code>set myMail add 1.000.000 Taler</code><br>
  407. <code>set myMail send</code><br>
  408. <code>set myMail clear</code><br>
  409. </ul><br>
  410. </ul>
  411. <a name="MSGMailattr"></a>
  412. <b>Attributes</b>
  413. <ul>
  414. Almost all of these attributes are not optional, most of them could set at definition.<br>
  415. <li><a href="MSGMailFrom">from</a><br>
  416. sets the mail address of the sender</li>
  417. <li><a href="MSGMailTo">to</a><br>
  418. sets the mail address of the recipent</li>
  419. <li><a href="MSGMailsmtphost">smtphost</a><br>
  420. sets the name of the smtphost, for example for GMX
  421. you could use mail.gmx.net or for Googlemail the smtphost is
  422. smtp.googlemail.com</li>
  423. <li><a href="MSGMailsmtphost">smtpport</a> (optional)<br>
  424. sets the port of the smtphost, for example for GMX
  425. or for Googlemail the smtport is 465, which is also
  426. the default and do not need to be set</li>
  427. <li><a href="MSGMailsubject">subject</a> (optional)<br>
  428. sets the subject of this email. Per default the subject is set to "FHEM"<br>
  429. </li>
  430. <li><a href="MSGMailauthfile">authfile</a><br>
  431. sets the authfile for the SSL connection to the SMTP host<br>
  432. the authfile is a simple textfile with the userid in line 1 and
  433. the password in line 2.<br>
  434. Example:<br>
  435. <code>123user45</code><br>
  436. <code>strenggeheim</code><br>
  437. It is a good behaviour to protect this data and put the file, for
  438. example into the /etc directory and set the rights to 440
  439. (chmod 440 /etc/msgmailauthfile), so that not everyone could see the contents
  440. of the file. FHEM must have access to this file to read the userid and password.
  441. <br>
  442. </li>
  443. <li><a href="MSGMailmailtype">mailtype</a> plain|html<br>
  444. Use this attribute to select the contenttype to text/plain or text/html.
  445. If text/html is selected, valid html code must be provided as content. No checks are applied!
  446. Per default this attribute is 'plain'</li>
  447. <li><a href="MSGMailCR">CR</a><br>
  448. set the option to write a carriage return at the end of the line.
  449. CR could be set to 0 or 1, 1 enables this feature.
  450. Per default this attribute is enabled</li>
  451. <li><a href="#loglevel">loglevel</a></li>
  452. </ul>
  453. </ul>
  454. =end html
  455. =cut