From 405186c86642568b1b4028e27a909a16fd90e78a Mon Sep 17 00:00:00 2001 From: Vikram Dattu Date: Mon, 13 Jan 2020 18:23:55 +0530 Subject: [PATCH] Added simple SMTP email client. This is based on `ssl_mail_client` from mbedtls. The client also adds functionality to send attachments. Signed-off-by: Vikram Dattu --- .../main/openssl_client_example_main.c | 7 +- examples/protocols/smtp_client/CMakeLists.txt | 9 + examples/protocols/smtp_client/Makefile | 10 + examples/protocols/smtp_client/README.md | 78 +++ .../protocols/smtp_client/main/CMakeLists.txt | 2 + .../smtp_client/main/Kconfig.projbuild | 33 ++ .../protocols/smtp_client/main/component.mk | 5 + .../protocols/smtp_client/main/esp_logo.png | Bin 0 -> 30977 bytes .../smtp_client/main/server_root_cert.pem | 25 + .../main/smtp_client_example_main.c | 513 ++++++++++++++++++ 10 files changed, 678 insertions(+), 4 deletions(-) create mode 100644 examples/protocols/smtp_client/CMakeLists.txt create mode 100644 examples/protocols/smtp_client/Makefile create mode 100644 examples/protocols/smtp_client/README.md create mode 100644 examples/protocols/smtp_client/main/CMakeLists.txt create mode 100644 examples/protocols/smtp_client/main/Kconfig.projbuild create mode 100644 examples/protocols/smtp_client/main/component.mk create mode 100644 examples/protocols/smtp_client/main/esp_logo.png create mode 100644 examples/protocols/smtp_client/main/server_root_cert.pem create mode 100644 examples/protocols/smtp_client/main/smtp_client_example_main.c diff --git a/examples/protocols/openssl_client/main/openssl_client_example_main.c b/examples/protocols/openssl_client/main/openssl_client_example_main.c index 8e917e66d..bd808fa34 100644 --- a/examples/protocols/openssl_client/main/openssl_client_example_main.c +++ b/examples/protocols/openssl_client/main/openssl_client_example_main.c @@ -37,10 +37,10 @@ static void openssl_example_task(void *p) struct sockaddr_in sock_addr; struct hostent *hp; struct ip4_addr *ip4_addr; - + int recv_bytes = 0; char recv_buf[OPENSSL_EXAMPLE_RECV_BUF_LEN]; - + const char send_data[] = OPENSSL_EXAMPLE_REQUEST; const int send_bytes = sizeof(send_data); @@ -134,7 +134,7 @@ static void openssl_example_task(void *p) recv_bytes += ret; ESP_LOGI(TAG, "%s", recv_buf); } while (1); - + ESP_LOGI(TAG, "totally read %d bytes data from %s ......", recv_bytes, OPENSSL_EXAMPLE_TARGET_NAME); failed5: @@ -172,7 +172,6 @@ static void openssl_example_client_init(void) void app_main(void) { - ESP_ERROR_CHECK( nvs_flash_init() ); ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); diff --git a/examples/protocols/smtp_client/CMakeLists.txt b/examples/protocols/smtp_client/CMakeLists.txt new file mode 100644 index 000000000..bc6c2b895 --- /dev/null +++ b/examples/protocols/smtp_client/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(smtp_client) diff --git a/examples/protocols/smtp_client/Makefile b/examples/protocols/smtp_client/Makefile new file mode 100644 index 000000000..b0d979618 --- /dev/null +++ b/examples/protocols/smtp_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := smtp_client + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/smtp_client/README.md b/examples/protocols/smtp_client/README.md new file mode 100644 index 000000000..55e8ec3ed --- /dev/null +++ b/examples/protocols/smtp_client/README.md @@ -0,0 +1,78 @@ +# SMTP Client Example + +The Example is SMTP client demo. It sends and email with attachment to recipient. + + +## How to use the example + + +### Configure the project + +``` +idf.py menuconfig +``` + +* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../README.md) for more details. + +Please set the following parameters under example config for SMTP client demo to work: + - Email server, port, sender's email ID, password, recipient's email ID. + + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + + + +## Example output + +``` +I (3212) smtp_example: Loading the CA root certificate... +I (3212) smtp_example: Setting hostname for TLS session... +I (3222) smtp_example: Setting up the SSL/TLS structure... +I (3232) smtp_example: Connecting to smtp.googlemail.com:587... +I (3542) smtp_example: Connected. + +220 smtp.googlemail.com ESMTP r62sm20390571pfc.89 - gsmtp +I (3952) smtp_example: Writing EHLO to server... + +250-smtp.googlemail.com at your service, [182.75.158.118] +250-SIZE 35882577 +250-8BITMIME +250-STARTTLS +250-ENHANCEDSTATUSCOD +ES +250-PIPELINING +250-CHUNKING +250 SMTPUTF8 +I (4262) smtp_example: Writing STARTTLS to server... + +220 2.0.0 Ready to start TLS +I (4562) smtp_example: Performing the SSL/TLS handshake... +I (5692) smtp_example: Verifying peer X.509 certificate... +I (5692) smtp_example: Certificate verified. +I (5692) smtp_example: Cipher suite is TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 +I (5702) smtp_example: Authentication... +I (5702) smtp_example: Write AUTH LOGIN +I (6002) smtp_example: Write USER NAME +I (6302) smtp_example: Write PASSWORD +I (6822) smtp_example: Write MAIL FROM +I (7132) smtp_example: Write RCPT +I (7432) smtp_example: Write DATA +I (8252) smtp_example: Write Content +I (10202) smtp_example: Email sent! +``` + + +## Note: + - You might need to enable permission for less secure apps from email provider. This is because these email providers (e.g. gmail) insist on OAUTH authorization. diff --git a/examples/protocols/smtp_client/main/CMakeLists.txt b/examples/protocols/smtp_client/main/CMakeLists.txt new file mode 100644 index 000000000..4a0228594 --- /dev/null +++ b/examples/protocols/smtp_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "smtp_client_example_main.c" + EMBED_TXTFILES server_root_cert.pem esp_logo.png) diff --git a/examples/protocols/smtp_client/main/Kconfig.projbuild b/examples/protocols/smtp_client/main/Kconfig.projbuild new file mode 100644 index 000000000..1d8d11cf7 --- /dev/null +++ b/examples/protocols/smtp_client/main/Kconfig.projbuild @@ -0,0 +1,33 @@ +menu "Example Configuration" + + config SMTP_SERVER + string "Mail Server" + default "smtp.googlemail.com" + help + Target domain for the example to connect to. + + config SMTP_PORT_NUMBER + string "Mail port number" + default "587" + help + Mail port number for the example to connect to. + + config SMTP_SENDER_MAIL + string "Sender email" + default "sender.email@gmail.com" + help + Sender's Email address + + config SMTP_SENDER_PASSWORD + string "Sender password" + default "password@123" + help + Sender's email password + + config SMTP_RECIPIENT_MAIL + string "Recipient email" + default "recipient.email@gmail.com" + help + Recipient's email + +endmenu diff --git a/examples/protocols/smtp_client/main/component.mk b/examples/protocols/smtp_client/main/component.mk new file mode 100644 index 000000000..f52e1c7db --- /dev/null +++ b/examples/protocols/smtp_client/main/component.mk @@ -0,0 +1,5 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# + +COMPONENT_EMBED_TXTFILES := server_root_cert.pem esp_logo.png diff --git a/examples/protocols/smtp_client/main/esp_logo.png b/examples/protocols/smtp_client/main/esp_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5a0514f7b12e9c3a4779decd57f222bc5b316efa GIT binary patch literal 30977 zcmc#)Q+IAXw61O2w)qxcZQItaZQHhO+t~H4ZQFKxdd{CX7waNxBqJkpjwJIzA{6Ar z;bCxKfPjGDB_%|ZfPjFp{`Z3d`TYZR>AL|0bGCtPrjIo5MS=(!SGrlGAkx~2jtEGENp01R{a^vhAIO1v5j2p<=)KcKu5yQXBxooC z#YdFI@)_S?ln5g#52?s#3enJrPzg!0yB!d+M8QP3VWxNq1@!}ugcFfO85HUWG7|8^ zC?b-1g;PWlYNH3r2(rXe*z#`zK@>?mIEnx5VB}AhkSIX{@&DKWD(!=e7eR_AlYG)R z7BR9*QDRYeV?+Le^gDZIg+5ClQxXa!6OBF4m3!@?+82W)5e2qN_`VN1jRV~H@s{G{ zV-ym4OD&Lr6M}_o$3as>N?J(=a`Xgq?jpO9=y@O>)NmjH}>rt{)kI`6^C{YBp z$)Jf092{@iCOn|<=Z!8Bcj|Tz@3Urk<6i|X{adT<-Wut`3toY&5T8TK#~6yIi#iXY z@IW^vi%7Nyy8nCPc%#PA&0d=W7C)9?DZZZ+EMoT6=%^X@vah^R(fcsHpb-m4o#fN#1@q>VZUWV+h zeXzZ1?#w*G%|2>pgg0?0FGWwO{DCoVfYZ{&IM zr9h3cuD+fn8XK12ZDs4SvWDa5JL)XV?(*mJ?2nbdo?r<~x|08rBu|K7q69Qp@k+$t-#tXoaT_rzAPV%vMakSA6HyE^y|*kMu{JjPKI;v~ zPU>$6i6_)8I8@n^z%7KosUh-TlcZIZwd*^stdY!O6XiWe${eOgCzfd5p?M=Jms=j} zp&6sHIfJ?#>$)zDb=hh2e(ZgE$#aj&P^*woOD$H$G+;IRV?0#K8f#olL9!;?x&vGs zzcJ;-mn>F=!%y+F$f_)tTgZTQifF$3)?Xm31o94ml3@B=>@+oA_^@;Fe&Al&l&DZi zmigW(K|LqFAU&Hm)u)I+Lox8UrT}~9Nha@+x=lyhjox*Qz!NVsJ&)z#czU#~7{!~Z zT>5r+Tq{^H-wgucA&$#$@|0irklx_e+^ebjzL}?%EWQKcIkjkjX|cB<~oYKFFrI62i+p`n!D;`{k(&t&9iRF!pGe7Ep_hO z*y+O8EXUK)x`zIIQO1)gQ5nE^@O2MW;Bl}V1WHS0I;^O8X5s7>bx780ft3jhf)#0C z0Q%{|2srFj?7FG<#Eg|CBu6Uxv0MHxJEq|73YF3)ZnhQ5;ZT#7z1uCi>Zoo-TsjI$I zQdjS?s^;Fh@?K@_qsq;m?3KS(VZU-T97KHpWU9J5|L}RD5%BPvds$HYIC~gDuJ85Z zl*De~jwaM1@iOEebSEnnRz)9dfc?L@07A%W@fByyd zgGeE{rz!7(562sxo#-TUUy+!KnF%aF<1ap*)UnS9*nS*AMy4pj4F6Yqh955yxR4G} z)l0S`yAX((NzzG5iLvRLw^t8sp#)>#3I^XCpbQypnMVz)abj!ZdbMu3(K*-qMrgjK zW@p^9TR&izKwDoNsW}Ueq7XPa?$vFrXLzo8a-!qh!P5tXpm-|J1C6)RY~`^98|PI; zgH1#~9;Cj&OB4YH=UbMeRw0@$hpG%4Ovas+A~`eBv-iAt#%kz~-F`nt+@ z?RMHjU@(A7Ay41=K&-ka=$=ota$k$p>hoo}{mgN3>__MKZT0oQxl+rrZBz(^CY5M7 z7gm^M9zH~ZR4GT9lmLC^yw-9a2tub$oVI_qcHuhvoVTnd;F#xh(HAYpSond}XZRNw z5+x!2FVOOjzxFl0`$G@>5jhyhTWPn@HB}_@KTWxR{sgc{l>|b&3Nl3cn4#5^Q@4MZ zxYc`rYo_UD`P?0`^bE+)y?Tw$tuMAdXYoCpcsySkJ%$E}Pw^^*gN1~yCHpSH2b~B2 zv-3173M)BJC5kQ~P8+BA?4hKW_6uTKLTHG*HQAQV5*XIr(n@nybpy?ekT4 zv`wFz!`45+ngv^<8#?mxBYQ|rA}-9W15G&0RG*#lA{!RV`g?c{aSl6d&&q23s~_-? zMhGm7Npk`jkm)9f1l>?h`V=@B#-JnOYUb&>m2%FBu>`P(EK)=5*+co1s}fby(D@vD zs1PTlg#k!P(1i;Xu`aIn$&*9H=9t>(`>lUZVP5N+f}ND4Se4&DX$rcGm_zaBz1Kr~ zaBih=+_@KK<%eIb1q12T^p7voYpOgPf)>v}HftuF>~jD~#=nYyI0iLkI#cE`UzW6C zb%50B=`QpnNweE^!yTf4s$ENXBd#ttravW>!udXOi@Gv&w>ZSrL4zFDUyo4bEf{`Z zoh;9&Yx(L;ws05&FTK#q9sWJR=SVPAF3nEzdjQBPO2Tc@^nl}aODH&h*a=+qI>okC z2F_t4STm4wB>+jfXQ?`j8Io@6P;-{Pl1_9MB}qR?>(zRk7u*^{5T0;vUrl$umj<|f z#>MvWU=n*;_)MQyrp~Ch`gG2#_Tp1{CZimIJWc79S>_WsanR(gHNGLr-$heQuHahZ)=$0(o24U2?( ze)UzZI|>C&kYc$%0(eJ}!tpke&5jRt zq-cF=xH!M_VSSt0o91FzNVoo+u3$hDM=Wol^WJ^}Va<5=xtNmq?mvpblZz+GJv-&f zfV;I`qq9^?%9T2uP)#&}x!_b*`}V*;wsgMl?8xnUes>naM*O#I;V$IZUVy;BM(F(fFMq*vs#Shzs1FU zzkXzAT^A}Xa5rV_ddgGYsulvV@fFmV&eW{f_I$H=sgl!-^p8r?5`QgDabIi_N9~Y4 ziRI4|w!cs*hJ$T6V>J2RuZgy8MNO`OxuaG(gMO#XvE9G9u9tZTLAZj$tAPK zfRI&2$wz>&t)+N)h_>xO{wv$mzAlBU5abyc6lPnX<@pc!?*vxN^8M1Nc7TP}H^+bP zrXkK{4Vb=35z>6euk)O0vuhoJLkRMFKq5vmj3(n_kcha=yO#Nr;W=*tAE2G-L8fliVAm#b zsO<&94E-%we7^TmALMM_&snV-{!)U1blo=#OG)p%D(C@%+cprm7@h&;0SN}YA+{t z8(Z#F8Y?Hk&SsPSV?O6)XU~Zz;WR2NGpwVSngRQH1jb{|VB1!pzI|~7g#?V29ugxI4tWVpY{);|a|Ge73*~ozaOY1Q+r@&NH&p&^Wwai?eu6wQ>Z$O><__ zHa$i-N(+ne!)mFu&cop;%sj)jMrf3jxc{4ph7RZcoTb%(X!aSAh~bGgAzyupoR*HE z(+wECx)~TI`%bF{y=?|1^MX30vaJig*d8fEoOthMiSo98LPp@bSaFh{(fd`ChA*kf z`Sl=n}JEcEaG z##bYSx4%yn=akPMvg>$f@BM;PtLQ@5BK4LqUg86&wJW$om2L7^*Y&A9u2;6}kq6k7 z(CJZ)O%aFw%t3uBOV#&9C;u4PVa*~?3CiAbHT*xuZpm}F;1wsqzkH7yNQQzrONqXB z=$c70!HTQ@$&lJ3SLu0G7$0>pQB~Oi$0l&UAh7hSMg;0vo2os}g3G7%K2#Vz8fFiR z^n=sxvlDQ;W5v`y<*0MW3oK~$lFDrZ4pBK{dA(-`)xiXPRrJ-?NyTD(Nl;+QlMwM0 z?H_W$khv^Sc(K-fe>x2Jgqg@&Z#ZuRVF3}?W3{V=$wP5?EuT@qZv134DtOO~;#}+r z>ha9Q?VR5&SA`2%E8ftu6vZv0?5!M{Fwi|1QRkT zAhhg6&mo5i@Hr|A_ELWDJuDa~Z$I?Nv$uva&D zb?xtYc^_?1mr&5KzWBq2Bm=NjP?9ZoZN9%xH|%D2&;R0bFGcW$q|Is<+OL*^{U$I-B7F`Tn&>_Ql~hJ zC{ZSm$N?WflnH@||8^YK@jw9f7o|a%5hZ`jVI+P3^C8U!Q=Z-Kb7O}5Ook%8f@o|p zGjUWupS4j`K9J{bpNGPr?B2EOI32Z#u|0UTEq8kRC7z5CB?a~xbv;wT>x$|c>HXAi z`U@W;r!bs$ta+MWTz2hhO=ZRSnR|4$|9$$8g z0PK5BmG2cu)+H81S1rlm@-^YcFM;ELX4-7%`&xn@!KadjeXlWXrnmaJr>&k=UWkF1 z(*5|5)v-3~0<=_jAG+if9%^K9&8Ff+P9EEh3z;eakC(u@YXVnaZ>(H}*&gO?V>3bI zq}Fd`XlJ*)ijAMeYT)ozKw*7mK-rfbiwCvOl;>+!JP-HvaMBm7FBu*|cUX|QcLYJ$OY)g=4wNY#=DI8$uF=rPZ>ug&YH@x!^iW(@;JFOdNR zRdVgrk2UX!NS_wG@__};^ERkZ1O>X376U0djaX5EGXw{_ZfY*Iw6ysG>sqvohghkM zU2%)nZGw+`5N#6DH;O(aXoZAIw(73SkXWqN^V;>>!#clrnCc(VOqf0oAYP87>gn10 zbEoblp4U0^0Uq>r6WK>W1h=dF0uQ%;J5C!ea8)Uu`dp5GLnNrURQb!PvAE=i{M{@@50-y*PjRXjo;-{_vl|84&Aw9<|mAWz$N6BI{rKK#E8TY{8 z6N4nf{V3?K899^ZPamh~?pZ(nl;{yxco}M(k7vV6!l6s_-r>hS#ehHpafN~dO=GE# zdrr6ZY~egl>_5j!_Ry6mh3QV)&JQzxWCTQGZg+n6DY|1rj_`p}`RT|pDzH=;2^c6O=-hAi9OWo~ToPF_+Gi-V!5UzlWQ zHgU^s0s19FysS|`^xd!Uo_6QA9&C5DaST6tpsu0Z5&y`r z`#lf1wsk#rs26AvL#IhtKVeK~*=^j0&8~|y&ULsNv(t@FIsiLdI!;K7$Lid*$P^KK z7AG7`+E8!wUKLUoPhwZ4(lY~9=0a+Uy*mN&2?HM@^cYQGXW?3SMD+?B$C$3t9oCtR zm&k=R>;tjdO}>FAsL=Gq=c8@R*QPsG+S@*dPGgX5qlVUTj}fklsY_-U6V9Ab)}&XF z%C+yVb9v34Xmoi-!p%!mE%4WDLgMPz{hD)8mHn##FwkD!?3K2Uf-bFX zt-ls7r*#Pq zt?Qp>>-RKS>w=c3-@QB%l}G}VUAk5i3yRch2k0+fFvTT2Ec70ZYA?)~q>cs_8Zj*$ zxQgL*fb%~RtYREjPZyKqr8C$>wOOG0Xtz}ZKj1cR;|ecQ`?P(2Y7D+ns9QPoYgo@FO_r|?RKuxuxM-z7*eSxsNa3cMKg7xwRmmwNnH z?dQY{j^T1Pg6y};r&HXLM8l4x%qH3S^VQ@!u%%rlyYBcj9Ftf3s72qtPRc-=HVXoi z@(fbOx;uzX&e%;&qe7xUNCHJCxK|@KsZMy){pE?Ek+@2ZwGc2+4_CwC_FU7ibEOZe z!a`D@w$T_EkYS!H4YtlZcwc7vWF9NvTe>)Bibu|ZR3!3`Dks3ct9r+~^`n%QBiF@f z*!ZD_w^f)Hwq^mk=EemA)if7iI#xAFZCnX82^+S<8=%8doUYc>cahuSu1?%~63(dm zoU!mYnhiwj{*>oJgr1O}aPC+t!>^r@fiHs>z`guBeNk^>;T(1zomK>eCju#h{1XKI zecK1=7koqjh z9;PCOa8o}(lw&XWAx&*_)7yKo=e5Td*`Xk$7nWURhi1ysa1-NJ6vBqvxckV3P_2!< zz%V%T;)QRTxj_G?&!P5*U4=J7a3|XD-FUm677pN!woHGGx*W4F{%Pp51;;k`LvO)6 zR8U1$+1B`eq#HL1(zmMxJ6hs|=MR4MlOA>M+#`{jGpyAKYoxHtCw{;*M+ z_N&}gA#DL+s*mj+ZMerEg%vW)a5~NjV5Fb6wmOmkD?P&WXyOy2`g!ZB^#Cyv#Y3;Xtn=YVw}yhe{643pGIB3zUQ`3O@Qw97Q=rrf}ZDja?S^TBy_6Jvj^ zT!VTG{sH(uFfY?#*ZvYdbgF17om0?%u^3>Hc%Ks=XQ**;4ljyWreZc-XEY0=H$}_M zcju+Vd!cme!6_-RSYLab)ly3f7cpD;8#nC@2Vdj?!q^f@_T1o14!}-uVjkaurgsKQ zvkB(|!f2rPN1EJ%)4@3X+vE6bTp>dJKX}9Or>$fM*(ldqoW+qZnWcHTcX3E!B4^g(AMJFkPl-6~Xo8Hf4 z^OlJ9zr&6nDnJ=;h6GmU-20oNgw`}p54-V&#e41uCL=?cpu{|y zKqjo6gD04OVA+$_e;ZVOlHZ(pMic+V3R^P&-JQlQkHL@h%){}bhrgBq8M=XT@cyZ^$@*#de#*MKdAgp^4IF~_Mp~mr`gB1lI)f9GuGF!+#ngjT3dC}|u_)pKbCFGAUciLV}S52F1 za1c~T&D)HD42juyEQS%(;seP1vw%H%eB{hW)$?WpOSGYw`baO_Sqj)&A9gMm?4HTCbogf81A5T?x;Jt1hx_Jp_Wzr?a4*9Dx+*YCg zd-PiU0CK)gmKrWsc_v)BO!pm${jhc7;BYnLA^SZNxAo`K5C|Jjb?4(rJU0zzFl`ym z;GSFbZ+v&<9swMz1b@gaQR7w!e9>;~|8}e@Tp~hT+n-mMgH`{H*!sj5sB^j5#L2G% zlM#PmSp#J$%Z1D%&f+g%!-mR!USVg#8z6#;Rx-rn?7a=$5bt?e3s3n2bL}VOw0K7{ zBvh9_Dfyn&|LhAb=xo%?lS!t%7d2dP9S|{H`!p%P z+fw>0pnS^a#vMbMTggC0hSGSb1Y}?sBCLp9(XVL%i8$3~^<3`8Pw+OE+0A=Q8y2(Q zqo)y>aPUr%pASSPi6L>;ntI-KV%>3Q{ZQ&xz{qAOg1TKLEAZZ4II4H3Xl1av0m*<> z^ot0X`>sw1jZ`gA0BFgI)Ky<>@uJjlkt;^rl+;}B}o`ip6oP!$t6RiL6Mt`tv zR=*yf8JWrQ8xCUh(lwk z9f@ZB&3l4DZ-rhFZxL8PD(!qMckSbNnwW1R$IZ65X^=E`m~j0@{oYEKotSeWJ5Rko zC;!)MY1H)T>Mf-!Iz^zxU`@zbZe$a&KO0{pbFq8dr!vQZ3{j@RkR^@% z)Ra5ua1-W^ul|H++6P@}eHjFrkXBh+owoAZqVTE5Vi)6S;DuD^DFxo7@CBP3t>Zrg z&@UH)IL}AJ%50rg_|(n5K=fD3jfSm;GrM{y@O560ktP2{$)pHSUA!l->BuK@ncHZo z6qj{P($rcypAXan>=S4b;Hk3hdFV)rSQSf)M4&>;i;@$KiPjHX$`}1z3K8|S4@k_w zVUTBa#2In|`--;~Oq{+~GKyHp?)>6B?ij%=Bt>8C;P3(wl3#5d&j}DPFZSYx&OG|K z@KV?Tdfjj-O?GHze-;P8NV@H}XMF@_G9J{AL5A8+h89QkJFD5d@yTU*0kcYx81u!DEl(t^QbF)=MAlY<@uCTvvh|l@Q2p8kXq$>Y zj-cp;$B5(8GSXoHu3u%!u#llcNSV&CM@VBw;FOOoQ|KfgQ3u0Ww8gmFsa51=1Q!eAb#jT6Ow(;ys?a)1q@Y z)j@-h;P|CjAOv1;3OFOPIsaipgHWsAPQiEmxM{;hlm9iz*v`*#CvoKdD?AW^odEQN z9u!&`X+ZGwJqLGM$VEe|qs^%#Wh&nzck8(d3?4cZcm1{&gpu=}n}G3wba`4@}7ETt~JqW=;tip`C!l?nadfCr_A+3y*-++H;9=Z zShlq0tE2ohFZ2kYDF~M5efp~m?U8*yW6b);!3Sik9U4s%OapfAimmP^hO-57@d5}M zepD_I7asE8v*P8fD5WBrQXIhSa6V2-&t00s0?M4@sgjp`d&~%qI`=B8Ro`3ez?|?u zKj%J<_5LnlZsnP&OqVFc(&ey?k`V2g^$}6|wy%5A|K^OqOo;;taHdGUR`2c$QHr-D zdwE7rf&773I&1-tIxm}me)ZYUhZmEX^SAf_yox!~Y#(3;B3r^?sPZ37l`yKv37&`S zIog%O?Kh-?`F`Ix6Gk?^)dl?;V!YPKtlDaKs{jG&h94OU?+eIn$2i}KHSg8!l=4f8 zqX#uU_cTbFaz&L@A?Cblt)e_8W`10$1|pQ%DQ`UvUU61uthputXyVy*tJ+DbpGAlF zkBNaGS(`YsNxJPKG1z~9ognFffhP-g=|y8)cj4P&bvoAF-?_6Vi9I(H+T@>ZHfT~} zW*Am|7gTe<*)tSqxMY=s2)rZdv1z!5Liz`8g<`s_A9BkGg5`^l%3g~?i{e0~z44m6 zu#h4|cqJwhl}ydl{4=$~xm#AuipumJO)IVlJ za6=1c>)YD%k54Ca?n8Gi^KrZ&={p7mY3nB8F(M#Uv1X4NB>5Nt;l^(ti1WDRA7w=G zFewCY?gsu!jSS{!B9MOnGiwC%e{TQX|&V!4b>^hU2f*FsgNS2C^Bi^o`T^nb}RTMRWJD5ei#td&eB`^O*4z~q8m}pF4 zNfdvPOPLD>UFG$~%RB9;C&*DvPA|pc34gkMx*!)-IW&^c`<$XOBKRKZx(f0)XO>Ig zdotMyR{hpht~&fRrdMrTc(wX6ivPksH90TjdBd85H%decgmWVsE6XtZ%Y1Z=?iy=x z{Y);aFrNe$o!XorC6!sQ1a@9bxjfyBgS=kkD#HRq3jZ1}-OfcOCu^9x;am3?LVFyt z$no18POwv(>mN30DcGb+Wc4V~u%Fdmb$%zltJ22XpXPZc*PlcDhxT)$*jg{)+$Dr7 zJ*%*IzM)QM^JeR^gAQOTY7UDnv$rIsrqqB7Cv)#<+yo)g2(Q(G%IW8oc)n-pZHX{h z&3%za61{34itv4+BjRPSz^Ib)oXbIg&tdbZ*LYWbE}`^~$3a!(Wfe{oV~?8yY)Y z`ss3+*+1FqzQkl0P~L5l`lkj@AoYl3DhUQ=JC2~-B_56Qv~6yQsk_&tMM^Ih z$I+$P()kPfyY$^*QgY4^848}7cT0KEi$;m<+^Gs+jJ;ddkK6Q_ZQjf5nB6rf=VJ(H zr8Zy4t$smc(qu|dX#b8^@M=Jxdku!$5ksWu3v6M~M;w1$qw^uZs9Yjcl-%53t3}~l z_lDboGGGCcEeoLqsnR75fWy!ANuGZa*SF4K%8g5BzzZMA#G#?1rQtoH@M;Y^EGAU@ zyY&qsq_0J2T;URqiG?KlR3Rw}o$Tc=P)qR$;nV*c70^{Nx3LK17XR#o0bDWZa9%(f zUL3IKmA|T=xck;US%>{T0z<=|)TT{(f>M8go9msw|3>){BJFGUiY#uf;{j^k6j}-? zWwN-wTx0oakL|nOWlT&`5ed=I@kTYnm>Cczv?u~{t|^vEN1t;O5HA4gx>9lnzy0LI*gCGsh!DXoL^YPjirPgC5mbPjPWhq^eiDd zAX=d6moA}CdkOkqZ^lcd5h=iglo^R%T`;2btmxl3hCtD;IP0+_WB~Qqw z%01|5QFWJg$WssKKJvZ~3}}y%R3Z_DMyW7on?&$9T)XUedB1dB%_6eC2HGSk(G0EV z{3AEn+0t$*tut~Fra#bihp?7kAaqKvWdOw#ZD3pHaYx7aP1Kc`k)k@ z@4%w5jve~kPHWpW-R#18s06qo|6y?QyoPt{m+KPiw1P6wi>rXdr2iiJ!}M5~a- zyvjHJBlwmgF&pCUj7~U$NWACO5ejbmOmiEuvZ+wN%e_5{>}{f>L>2y=de5!SgL6Lq zszy3E9dv1lc`BDaxM5uY4@fAM$g8uxYZ6>ItJ&~pFCA-i3Lqd$ct=eU5kkI!ST-O7 zS>?7D^Tfp!3F8xl;h&gOKg4`ID>uTl;n8+bv?xp5-k3$PVkaAQ=$;&3yV9##GH!6M z#>juLM}Rzez71?|d$l)Lb?CDUh)goDLWa}T@kh+A3Q zv$VWt<8ar;>8YXLUN)OvB<8{A?}`!Xc>%h;qsFy3_OkX?wemW(dgf)N$TS$YtYP`t&%eM8z(T}I@Q&VJ})#TmP zWK`DH3KJjJmJU{ybXrX-?|Np}u4uhNEb@rxOMgf{w{{Nltx93M$V0)NGCrn;ky0)d zS}7e?1qq)}R(!R@dF#HvrB%v!yCR+LO03cJyy-zoU-Ya$?Fsa*2rsq(DgA=bQ!ypcL(26EJKj|%mgHVwf<@MKuHTB(+9#z_W4ruka_|~~G0I?cs zal+vyi;gJ6$W^u%U4WHpf(216FH+_$91QI+Oa-6=)HAqx=;%xl+*~`3)(P~hA31W+ zifd|A6L^HXUhb_Y7^7iln3y|Z;-%(|ajW=tIBz>|A60hzaEMi)4LL%K5m@5nd1&ae zkX;;(GhfSLTU9Ga0;h3)Osp-vYz@+&5qH7}tHzsVZu{~bL|d{j{ZiuZ6D&;%Fo9$k z2F_FIq6zG!1wTpBtYKh|BPOUNjTn zRnT4kQD?g8r0M@)_$}m7-sy#d%8_{Cyis3 zNiWA{OE=Q=2LGH|L{Splqfh&kt^-Lm95Q}k!Ldn4hv5Ee8MxuJqoe8&QR}WyjNCAX zh>q>ZeKKRA`_$|8up@X0M&Il||E;sZ$?n_Tz1#T5p{TNc2f^20l#qPbFok_2kb1rL zxS2R!B_Ik6yqQwP(%>gDo(#d3vo!JB4Fbxg#rnI=a83JJ!0&GHlDL~df}kUCZqWG6 z>r`@26J_|zJDiNx*aW6{I8sPF>Zz-a?oV&^!bQrE7_K}Q`!7~uBM;)eI2uCxy_Xd1 zB?1-LE-1D)=N{*i>N#aj;eB( z-sx|lRr_vDE@)?*p4bJgnET%$FHjvXg~Vk6Ig9ZPV?mJ44xuG%JYscaT+Tiofw;tWy zd+pz9BZsUlsFZil@wN8ZQN6~07Dfq?a==}Md{_&yt2q0wFXpr5*l4M=AQv+Y7-4NO z<0cuw%fT2}!X+2(M6KLf0K24=v2>gVH8

ZhMed* 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 +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" + +#include "mbedtls/platform.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/esp_debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" +#include +#include + + +/* Constants that are configurable in menuconfig */ +#define MAIL_SERVER CONFIG_SMTP_SERVER +#define MAIL_PORT CONFIG_SMTP_PORT_NUMBER +#define SENDER_MAIL CONFIG_SMTP_SENDER_MAIL +#define SENDER_PASSWORD CONFIG_SMTP_SENDER_PASSWORD +#define RECIPIENT_MAIL CONFIG_SMTP_RECIPIENT_MAIL + +#define SERVER_USES_STARTSSL 1 + +static const char *TAG = "smtp_example"; + +#define TASK_STACK_SIZE (8 * 1024) +#define BUF_SIZE 512 + +#define VALIDATE_MBEDTLS_RETURN(ret, min_valid_ret, max_valid_ret, goto_label) \ + do { \ + if (ret < min_valid_ret || ret > max_valid_ret) { \ + goto goto_label; \ + } \ + } while (0) \ + +/** + * Root cert for smtp.googlemail.com, taken from server_root_cert.pem + * + * The PEM file was extracted from the output of this command: + * openssl s_client -showcerts -connect smtp.googlemail.com:587 -starttls smtp + * + * The CA root cert is the last cert given in the chain of certs. + * + * To embed it in the app binary, the PEM file is named + * in the component.mk COMPONENT_EMBED_TXTFILES variable. + */ + +extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start"); +extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end"); + +extern const uint8_t esp_logo_png_start[] asm("_binary_esp_logo_png_start"); +extern const uint8_t esp_logo_png_end[] asm("_binary_esp_logo_png_end"); + +static int write_and_get_response(mbedtls_net_context *sock_fd, unsigned char *buf, size_t len) +{ + int ret; + const size_t DATA_SIZE = 128; + unsigned char data[DATA_SIZE]; + char code[4]; + size_t i, idx = 0; + + if (len) { + ESP_LOGD(TAG, "%s", buf); + } + + if (len && (ret = mbedtls_net_send(sock_fd, buf, len)) <= 0) { + ESP_LOGE(TAG, "mbedtls_net_send failed with error -0x%x", -ret); + return ret; + } + + do { + len = DATA_SIZE - 1; + ret = mbedtls_net_recv(sock_fd, data, len); + + if (ret <= 0) { + ESP_LOGE(TAG, "mbedtls_net_recv failed with error -0x%x", -ret); + goto exit; + } + + data[len] = '\0'; + printf("\n%s", data); + len = ret; + for (i = 0; i < len; i++) { + if (data[i] != '\n') { + if (idx < 4) { + code[idx++] = data[i]; + } + continue; + } + + if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') { + code[3] = '\0'; + ret = atoi(code); + goto exit; + } + + idx = 0; + } + } while (1); + +exit: + return ret; +} + +static int write_ssl_and_get_response(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) +{ + int ret; + const size_t DATA_SIZE = 128; + unsigned char data[DATA_SIZE]; + char code[4]; + size_t i, idx = 0; + + if (len) { + ESP_LOGD(TAG, "%s", buf); + } + + while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret); + goto exit; + } + } + + do { + len = DATA_SIZE - 1; + ret = mbedtls_ssl_read(ssl, data, len); + + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + continue; + } + + if (ret <= 0) { + ESP_LOGE(TAG, "mbedtls_ssl_read failed with error -0x%x", -ret); + goto exit; + } + + ESP_LOGD(TAG, "%s", data); + + len = ret; + for (i = 0; i < len; i++) { + if (data[i] != '\n') { + if (idx < 4) { + code[idx++] = data[i]; + } + continue; + } + + if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') { + code[3] = '\0'; + ret = atoi(code); + goto exit; + } + + idx = 0; + } + } while (1); + +exit: + return ret; +} + +static int write_ssl_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) +{ + int ret; + + if (len) { + ESP_LOGD(TAG, "%s", buf); + } + + while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret); + return ret; + } + } + + return 0; +} + +static int perform_tls_handshake(mbedtls_ssl_context *ssl) +{ + int ret = -1; + uint32_t flags; + char *buf = NULL; + buf = (char *) calloc(1, BUF_SIZE); + if (buf == NULL) { + ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE); + goto exit; + } + + ESP_LOGI(TAG, "Performing the SSL/TLS handshake..."); + + fflush(stdout); + while ((ret = mbedtls_ssl_handshake(ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret); + goto exit; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0) { + /* In real life, we probably want to close connection if ret != 0 */ + ESP_LOGW(TAG, "Failed to verify peer certificate!"); + mbedtls_x509_crt_verify_info(buf, BUF_SIZE, " ! ", flags); + ESP_LOGW(TAG, "verification info: %s", buf); + } else { + ESP_LOGI(TAG, "Certificate verified."); + } + + ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(ssl)); + ret = 0; /* No error */ + +exit: + if (buf) { + free(buf); + } + return ret; +} + +static void smtp_client_task(void *pvParameters) +{ + char *buf = NULL; + unsigned char base64_buffer[128]; + int ret, len; + size_t base64_len; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_x509_crt cacert; + mbedtls_ssl_config conf; + mbedtls_net_context server_fd; + + mbedtls_ssl_init(&ssl); + mbedtls_x509_crt_init(&cacert); + mbedtls_ctr_drbg_init(&ctr_drbg); + ESP_LOGI(TAG, "Seeding the random number generator"); + + mbedtls_ssl_config_init(&conf); + + mbedtls_entropy_init(&entropy); + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + NULL, 0)) != 0) { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned -0x%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Loading the CA root certificate..."); + + ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start, + server_root_cert_pem_end - server_root_cert_pem_start); + + if (ret < 0) { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Setting hostname for TLS session..."); + + /* Hostname set here should match CN in server certificate */ + if ((ret = mbedtls_ssl_set_hostname(&ssl, MAIL_SERVER)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned -0x%x", -ret); + goto exit; + } + + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); +#ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log(&conf, 4); +#endif + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x", -ret); + goto exit; + } + + mbedtls_net_init(&server_fd); + + ESP_LOGI(TAG, "Connecting to %s:%s...", MAIL_SERVER, MAIL_PORT); + + if ((ret = mbedtls_net_connect(&server_fd, MAIL_SERVER, + MAIL_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { + ESP_LOGE(TAG, "mbedtls_net_connect returned -0x%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Connected."); + + mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + buf = (char *) calloc(1, BUF_SIZE); + if (buf == NULL) { + ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE); + goto exit; + } +#if SERVER_USES_STARTSSL + /* Get response */ + ret = write_and_get_response(&server_fd, (unsigned char *) buf, 0); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + + ESP_LOGI(TAG, "Writing EHLO to server..."); + len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32"); + ret = write_and_get_response(&server_fd, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + + ESP_LOGI(TAG, "Writing STARTTLS to server..."); + len = snprintf((char *) buf, BUF_SIZE, "STARTTLS\r\n"); + ret = write_and_get_response(&server_fd, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + + ret = perform_tls_handshake(&ssl); + if (ret != 0) { + goto exit; + } + +#else /* SERVER_USES_STARTSSL */ + ret = perform_tls_handshake(&ssl); + if (ret != 0) { + goto exit; + } + + /* Get response */ + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, 0); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + ESP_LOGI(TAG, "Writing EHLO to server..."); + + len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32"); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + +#endif /* SERVER_USES_STARTSSL */ + + /* Authentication */ + ESP_LOGI(TAG, "Authentication..."); + + ESP_LOGI(TAG, "Write AUTH LOGIN"); + len = snprintf( (char *) buf, BUF_SIZE, "AUTH LOGIN\r\n" ); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit); + + ESP_LOGI(TAG, "Write USER NAME"); + ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer), + &base64_len, (unsigned char *) SENDER_MAIL, strlen(SENDER_MAIL)); + if (ret != 0) { + ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret); + goto exit; + } + len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit); + + ESP_LOGI(TAG, "Write PASSWORD"); + ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer), + &base64_len, (unsigned char *) SENDER_PASSWORD, strlen(SENDER_PASSWORD)); + if (ret != 0) { + ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret); + goto exit; + } + len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit); + + /* Compose email */ + ESP_LOGI(TAG, "Write MAIL FROM"); + len = snprintf((char *) buf, BUF_SIZE, "MAIL FROM:<%s>\r\n", SENDER_MAIL); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + + ESP_LOGI(TAG, "Write RCPT"); + len = snprintf((char *) buf, BUF_SIZE, "RCPT TO:<%s>\r\n", RECIPIENT_MAIL); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + + ESP_LOGI(TAG, "Write DATA"); + len = snprintf((char *) buf, BUF_SIZE, "DATA\r\n"); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit); + + ESP_LOGI(TAG, "Write Content"); + /* We do not take action if message sending is partly failed. */ + len = snprintf((char *) buf, BUF_SIZE, + "From: %s\r\nSubject: mbed TLS Test mail\r\n" + "To: %s\r\n" + "MIME-Version: 1.0 (mime-construct 1.9)\n", + "ESP32 SMTP Client", RECIPIENT_MAIL); + + /** + * Note: We are not validating return for some ssl_writes. + * If by chance, it's failed; at worst email will be incomplete! + */ + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + + /* Multipart boundary */ + len = snprintf((char *) buf, BUF_SIZE, + "Content-Type: multipart/mixed;boundary=XYZabcd1234\n" + "--XYZabcd1234\n"); + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + + /* Text */ + len = snprintf((char *) buf, BUF_SIZE, + "Content-Type: text/plain\n" + "This is a simple test mail from the SMTP client example.\r\n" + "\r\n" + "Enjoy!\n\n--XYZabcd1234\n"); + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + + /* Attachment */ + len = snprintf((char *) buf, BUF_SIZE, + "Content-Type: image/image/png;name=esp_logo.png\n" + "Content-Transfer-Encoding: base64\n" + "Content-Disposition:attachment;filename=\"esp_logo.png\"\n"); + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + + /* Image contents... */ + const uint8_t *offset = esp_logo_png_start; + while (offset < esp_logo_png_end - 1) { + int read_bytes = MIN(((sizeof (base64_buffer) - 1) / 4) * 3, esp_logo_png_end - offset - 1); + ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer), + &base64_len, (unsigned char *) offset, read_bytes); + if (ret != 0) { + ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret); + goto exit; + } + offset += read_bytes; + len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer); + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + } + + len = snprintf((char *) buf, BUF_SIZE, "\n--XYZabcd1234\n"); + ret = write_ssl_data(&ssl, (unsigned char *) buf, len); + + len = snprintf((char *) buf, BUF_SIZE, "\r\n.\r\n"); + ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len); + VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit); + ESP_LOGI(TAG, "Email sent!"); + + /* Close connection */ + mbedtls_ssl_close_notify(&ssl); + ret = 0; /* No errors */ + +exit: + mbedtls_ssl_session_reset(&ssl); + mbedtls_net_free(&server_fd); + + if (ret != 0) { + mbedtls_strerror(ret, buf, 100); + ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf); + } + + putchar('\n'); /* Just a new line */ + if (buf) { + free(buf); + } + vTaskDelete(NULL); +} + +void app_main() +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /** + * This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + xTaskCreate(&smtp_client_task, "smtp_client_task", TASK_STACK_SIZE, NULL, 5, NULL); +}