2 % File: smtp.erl (~jb/work/yaws/applications/mail/src/smtp.erl)
3 % Author: Johan Bevemyr
4 % Created: Tue Feb 24 23:15:59 2004
8 -author('jb@bevemyr.com').
15 % smtp:send("mail.bevemyr.com", "jb@bevemyr.com",
16 % ["katrin@bevemyr.com","jb@bevemyr.com"],
18 % "My Message", [{"file1.txt","text/plain","hej hopp igen"}]).
22 send(Server
, From
, To
, Subject
, Message
, Attached
) ->
23 {ok
, Port
} = smtp_init(Server
, From
, To
),
24 Boundary
="--Next_Part("++boundary_date()++")--",
26 [mail_header("To: ", To
),
27 mail_header("From: ", From
),
28 mail_header("Subject: ", Subject
)],
29 Headers
= case Attached
of
31 [mail_header("Content-Type: ", "text/plain"),
32 mail_header("Content-Transfer-Encoding: ", "8bit")];
34 [mail_header("Mime-Version: ", "1.0"),
35 mail_header("Content-Type: ",
36 "Multipart/Mixed;\r\n boundary=\""++
38 mail_header("Content-Transfer-Encoding: ", "8bit")]
40 smtp_send_part(Port
, [CommonHeaders
, Headers
, "\r\n"]),
45 smtp_send_part(Port
, ["--",Boundary
,"\r\n",
46 mail_header("Content-Type: ",
47 "Text/Plain; charset=us-ascii"),
48 mail_header("Content-Transfer-Encoding: ",
52 smtp_send_message(Port
, Message
),
55 smtp_send_part(Port
, ["\r\n.\r\n"]),
58 smtp_send_attachments(Port
, Boundary
, Files
),
59 smtp_send_part(Port
, ["\r\n.\r\n"]),
64 smtp_send_attachments(Port
, Boundary
, []) ->
65 smtp_send_part(Port
, ["\r\n--",Boundary
,"--\r\n"]);
66 smtp_send_attachments(Port
, Boundary
, [{FileName
,ContentType
,Data
}|Rest
]) ->
67 smtp_send_part(Port
, ["\r\n--",Boundary
,"\r\n",
68 mail_header("Content-Type: ", ContentType
),
69 mail_header("Content-Transfer-Encoding: ",
71 mail_header("Content-Disposition: ",
72 "attachment; filename=\""++
76 smtp_send_b64(Port
, Data
),
77 smtp_send_attachments(Port
, Boundary
, Rest
).
80 smtp_send_b64(Port
, Data
) ->
81 B64
= str2b64_final(Data
),
82 gen_tcp:send(Port
, B64
).
85 dat2str_boundary(calendar:local_time()).
87 dat2str_boundary({{Y
, Mo
, D
}, {H
, M
, S
}}) ->
89 io_lib:format("~s_~2.2.0w_~s_~w_~2.2.0w:~2.2.0w:~2.2.0w_~w",
90 [weekday(Y
,Mo
,D
), D
, int_to_mt(Mo
),
91 Y
,H
,M
,S
,random:uniform(5000)])).
94 smtp_init(Server
, From
, Recipients
) ->
95 {ok
, Port
} = gen_tcp:connect(Server
, 25, [{active
, false
},
98 smtp_expect(220, Port
, "SMTP server does not respond"),
99 smtp_put( smtp_from(From
), Port
),
100 smtp_expect(250, Port
, "Sender not accepted by mail server"),
101 send_recipients(Recipients
, Port
),
102 smtp_put("DATA", Port
),
103 smtp_expect(354, Port
, "Message not accepted by mail server."),
109 smtp_expect(250, Port
, "Message not accepted by mail server."),
113 smtp_send_part(Port
, Data
) ->
114 gen_tcp:send(Port
, Data
).
116 smtp_send_message(Port
, Data
) ->
117 {_LastNL
, Escaped
} = dot_escape(Data
, true
),
118 gen_tcp:send(Port
, Escaped
).
120 send_recipients( Recipients
, Port
) ->
122 smtp_put( smtp_recipient(R
), Port
),
123 smtp_expect(250, Port
, io_lib:format("Recipient ~s not accepted.",[R
]))
125 lists:foreach( Fun
, Recipients
).
127 smtp_put(Message
, Port
) ->
128 gen_tcp:send(Port
, [Message
,"\r\n"]).
130 smtp_expect(Code
, Port
, ErrorMsg
) ->
131 smtp_expect(Code
, Port
, [], ErrorMsg
).
133 smtp_expect(Code
, Port
, Acc
, ErrorMsg
) ->
134 Res
= gen_tcp:recv(Port
, 0, 15000),
137 NAcc
= Acc
++binary_to_list(Bin
),
138 case string:chr(NAcc
, $
\n) of
140 smtp_expect(Code
, Port
, NAcc
, ErrorMsg
);
142 ResponseCode
= to_int(NAcc
),
144 ResponseCode
== Code
-> ok
;
145 true
-> throw({error
, ErrorMsg
})
152 %% add smtp from prelude. add <> around address (if needed)
153 smtp_from( Address
) ->
154 lists:append( "MAIL FROM: ", add_angle_brackets( Address
) ).
156 %% add smtp recipients prelude. add <> around address (if needed)
157 smtp_recipient( Address
) ->
158 lists:append( "RCPT TO: ", add_angle_brackets( Address
) ).
160 %% make sure the address has <> around itself
161 add_angle_brackets( Address
) ->
162 add_angle_bracket_start( add_angle_bracket_close(Address
) ).
164 add_angle_bracket_start( [$
<|T
] ) -> [$
<|T
];
165 add_angle_bracket_start( Address
) -> [$
<|Address
].
167 %% add > at the end of address, if it is not present
168 add_angle_bracket_close( Address
) ->
169 case lists:reverse( Address
) of
171 Reversed
-> lists:reverse( [$
>|Reversed
] )
174 %% Add an . at all lines starting with a dot.
176 dot_escape(Data
, NL
) ->
177 dot_escape(Data
, NL
, []).
179 dot_escape([], NL
, Acc
) ->
180 {NL
, lists:reverse(Acc
)};
181 dot_escape([$
.|Rest
], true
, Acc
) ->
182 dot_escape(Rest
, false
, [$
.,$
.|Acc
]);
183 dot_escape([$
\n|Rest
], _
, Acc
) ->
184 dot_escape(Rest
, true
, [$
\n|Acc
]);
185 dot_escape([C
|Rest
], _
, Acc
) ->
186 dot_escape(Rest
, false
, [C
|Acc
]).
190 %dot_unescape(Data) ->
191 % {_,Dt} = dot_unescape(Data, true, []),
194 %dot_unescape([], NL, Acc) ->
195 % {NL, lists:reverse(Acc)};
196 %dot_unescape([$.|Rest], true, Acc) ->
197 % dot_unescape(Rest, false, Acc);
198 %dot_unescape([$\n|Rest], _, Acc) ->
199 % dot_unescape(Rest, true, [$\n|Acc]);
200 %dot_unescape([L|Rest], NL, Acc) when list(L) ->
201 % {NL2, L2} = dot_unescape(L, NL, []),
202 % dot_unescape(Rest, NL2, [L2|Acc]);
203 %dot_unescape([C|Rest], _, Acc) ->
204 % dot_unescape(Rest, false, [C|Acc]).
209 str2b64_final(String
) ->
210 str2b64_final(String
, []).
213 str2b64_final([], Acc
) ->
215 str2b64_final(String
, Acc
) ->
216 case str2b64_line(String
, []) of
218 str2b64_final(Rest
, ["\n",Line
|Acc
]);
220 lists:reverse(["\n",str2b64_end(Cont
)|Acc
])
225 str2b64_line(S
, []) -> str2b64_line(S
, [], 0);
226 str2b64_line(S
, {Rest
,Acc
,N
}) -> str2b64_line(Rest
++ S
, Acc
, N
).
228 str2b64_line(S
, Out
, 76) -> {ok
,lists:reverse(Out
),S
};
229 str2b64_line([C1
,C2
,C3
|S
], Out
, N
) ->
231 O2
= e(((C1 band
16#
03) bsl
4) bor (C2 bsr
4)),
232 O3
= e(((C2 band
16#
0f
) bsl
2) bor (C3 bsr
6)),
233 O4
= e(C3 band
16#
3f
),
234 str2b64_line(S
, [O4
,O3
,O2
,O1
|Out
], N
+4);
235 str2b64_line(S
, Out
, N
) ->
240 str2b64_end({[C1
,C2
],Out
,_N
}) ->
242 O2
= e(((C1 band
16#
03) bsl
4) bor (C2 bsr
4)),
243 O3
= e((C2 band
16#
0f
) bsl
2),
244 lists:reverse(Out
, [O1
,O2
,O3
,$
=]);
245 str2b64_end({[C1
],Out
,_N
}) ->
247 O2
= e((C1 band
16#
03) bsl
4),
248 lists:reverse(Out
, [O1
,O2
,$
=,$
=]);
249 str2b64_end({[],Out
,_N
}) -> lists:reverse(Out
);
250 str2b64_end([]) -> [].
254 e(X
) when X
>= 0, X
< 26 -> X
+ $A
;
255 e(X
) when X
>= 26, X
< 52 -> X
+ $a
- 26;
256 e(X
) when X
>= 52, X
< 62 -> X
+ $
0 - 52;
259 e(X
) -> erlang:error({badchar
,X
}).
266 int_to_wd(calendar:day_of_the_week(Y
,Mo
,D
)).
268 int_to_wd(1) -> "Mon";
269 int_to_wd(2) -> "Tue";
270 int_to_wd(3) -> "Wed";
271 int_to_wd(4) -> "Thu";
272 int_to_wd(5) -> "Fri";
273 int_to_wd(6) -> "Sat";
274 int_to_wd(7) -> "Sun".
276 int_to_mt(1) -> "Jan";
277 int_to_mt(2) -> "Feb";
278 int_to_mt(3) -> "Mar";
279 int_to_mt(4) -> "Apr";
280 int_to_mt(5) -> "May";
281 int_to_mt(6) -> "Jun";
282 int_to_mt(7) -> "Jul";
283 int_to_mt(8) -> "Aug";
284 int_to_mt(9) -> "Sep";
285 int_to_mt(10) -> "Oct";
286 int_to_mt(11) -> "Nov";
287 int_to_mt(12) -> "Dec".
291 mail_header(_Key
, []) -> [];
292 mail_header(Key
, Val
) -> Key
++Val
++"\r\n".
299 to_int([D
|Ds
], Acc
) when D
>= $
0, D
=< $
9->
300 to_int(Ds
, Acc
*10+D
-$
0);
301 to_int(_
, Acc
) -> Acc
.