1 /******************************************************************************
2 *
3 * Copyright (C) 2009, The Gentee Group. All rights reserved.
4 * This file is part of the Gentee open source project - http://www.gentee.com.
5 *
6 * THIS FILE IS PROVIDED UNDER THE TERMS OF THE GENTEE LICENSE ("AGREEMENT").
7 * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE CONSTITUTES RECIPIENTS
8 * ACCEPTANCE OF THE AGREEMENT.
9 *
10 * Author: Alexey Krivonogov ( gentee )
11 *
12 ******************************************************************************/
13
14 include : $"..\socket\internet.g"
15 include : $"..\endecode\base64.g"
16
17 type smtp
18 {
19 str username
20 str password
21 socket sock // Главный сокет
22 uint notify // Функция для уведомлений
23 inetnotify ni
24 }
25
26 define
27 {
28 /* SMTP_OK = 220
29 SMTP_GOODBYE = 221
30 SMTP_HELLO = 250
31 */
32 ERRSMTP_RESPONSE = 1
33 ERRSMTP_QUIT
34
35 NFYSMTP_RESPONSE = $NFYFTP_RESPONSE
36 NFYSMTP_SENDCMD = $NFYFTP_SENDCMD
37 }
38 /*
39 type timetm {
40 int tm_sec
41 int tm_min
42 int tm_hour
43 int tm_mday
44 int tm_mon
45 int tm_year
46 int tm_wday
47 int tm_yday
48 int tm_isdst
49 }
50
51 import "msvcrt.dll" <cdeclare>
52 {
53 uint strftime( uint, uint, uint, timetm )
54 }
55
56 method str str.formatdate( datetime dt, str format )
57 {
58 timetm tm
59 int len
60
61 this.clear()
62 this.reserve( 64 )
63 tm.tm_sec = dt.second
64 tm.tm_min = dt.minute
65 tm.tm_hour = dt.hour
66 tm.tm_mday = dt.day
67 tm.tm_mon = dt.month - 1
68 tm.tm_year = dt.year - 1900
69 tm.tm_wday = dt.dayofweek
70
71 len = strftime( this.ptr(), 64, format.ptr(), tm )
72 if len > 0 : this.setlen( len )
73 return this
74 }
75 */
76 func str getdateformat_en( datetime systime, str format, str date )
77 {
78 uint locale
79 //#define LANG_ENGLISH 0x09
80 //#define SUBLANG_ENGLISH_US 0x01
81 //MAKELCID(lgid, srtid) = (srtid) << 16 | lgid
82 // SORT_DEFAULT 0
83 locale = 0x09 | (0x01 << 10 )//MAKELCID(lgid, srtid)
84 date.reserve( 64 )
85 return date.setlen( GetDateFormat( locale, 0,
86 systime, format.ptr(),
87 date.ptr(), 64 ) - 1 )
88 }
89
90 method uint smtp.notify( uint code )
91 {
92 if !this.notify : return 1
93
94 if !this.notify->func( code, this.ni )
95 {
96 ineterror = $ERRINET_USERBREAK
97 return 0
98 }
99 if code == $NFYINET_ERROR : return 0
100 return 1
101 }
102
103 method str smtp.lastresponse( str out )
104 {
105 return out = this.ni.head
106 }
107
108 method uint smtp.sendcmd( str cmd )
109 {
110 this.ni.head = cmd
111 if !this.ni.head.islast( 0xA ) : this.ni.head += "\l"
112
113 if !this.sock.send( this.ni.head ) : return this.notify( $NFYINET_ERROR )
114 this.notify( $NFYSMTP_SENDCMD )
115
116 return 1
117 }
118
119
120 method uint smtp.cmdresponse
121 {
122 uint ret i
123 buf data
124 arrstr lines
125
126 subfunc uint nodigit( ubyte ch )
127 {
128 return ch < '0' || ch > '9'
129 }
130 label again
131 this.sock.recv( data )
132 data += byte( 0 )
133 this.ni.head = data->str
134
135 if *data == 1 : return 0
136
137 this.ni.head.split( lines, 0xA, 0 )
138 foreach cur, lines
139 {
140 if nodigit( cur[0] ) || nodigit( cur[1] ) || nodigit( cur[2] ) ||
141 ( cur[3] != ' ' && cur[3] != '-' )
142 {
143 ineterror = $ERRSMTP_RESPONSE
144 break
145 }
146 ret = uint( cur )
147 }
148 /* if (lines[ *lines - 1 ])[ 3 ] != ' '
149 {
150 data.use--
151 goto again
152 }*/
153 if ret >= 400 || ineterror == $ERRSMTP_RESPONSE
154 {
155 if ( ret >= 400 ) : ineterror = ret
156 this.notify( $NFYINET_ERROR )
157 return 0
158 }
159 this.notify( $NFYSMTP_RESPONSE )
160 return ret
161 }
162
163 method uint smtp.command( str cmd )
164 {
165 if !this.sendcmd( cmd ) : return 0
166 return this.cmdresponse()
167 }
168
169 method uint smtp.close()
170 {
171 uint cmd ret = 1
172
173 if this.sock.socket
174 {
175 ret = this.command( "QUIT" )
176 /* cmd = this.command( "QUIT" )
177 if cmd && cmd != $SMTP_GOODBYE
178 {
179 ineterror = $ERRSMTP_QUIT
180 this.notify( $NFYINET_ERROR )
181 ret = 0
182 }*/
183 this.sock.close( )
184 }
185 return ret
186 }
187
188 method uint smtp.open( str host, uint port, str username, str password, uint notify )
189 {
190 uint rsp
191
192 .username = username
193 .password = password
194 .notify = notify
195
196 .sock.host = host
197 .sock.port = port
198
199 .ni.url = host
200 this.notify( $NFYINET_CONNECT )
201 if !.sock.connect() : goto error
202 if !.cmdresponse() : goto error
203 if *username
204 {
205 if .command("EHLO \(host)")
206 {
207 buf bpsw
208 str spsw
209
210 bpsw = '\i 0 \( username ) \(password)'
211 spsw.tobase64( bpsw->str )
212 if !.command( "AUTH PLAIN \(spsw)" )
213 {
214 this.close()
215 return 0
216 }
217 return 1
218 }
219 }
220 else : rsp = .command( "HELO \(host)")
221 if !rsp : goto error
222 return 1
223
224 label error
225 this.notify( $NFYINET_ERROR )
226 this.close( )
227 return 0
228 }
229
230 method uint smtp.send( str from, str to, str subject, str body, str exthead )
231 {
232 datetime dt
233 str date time
234 str data
235 arrstr toi
236
237 subfunc str getemail( str in out )
238 {
239 uint left right
240 left = in.findch('<')
241 right = in.findch('>')
242 if right < *in : out.substr( in, left + 1, right - left - 1)
243 else : out = in
244 out.trimspace()
245 return out
246 }
247
248 dt.getsystime()
249 getdateformat_en( dt, "ddd, dd MMM yyyy", date )
250 gettimeformat ( dt, " HH:mm:ss", time )
251 date += " \(time) +0000"
252 // date.formatdate( dt, "%a, %d %b %Y %H:%M:%S +0000" )
253
254 to.split( toi, ',', $SPLIT_NOSYS )
255 foreach curto, toi
256 {
257 str efrom eto
258
259 getemail( from, efrom )
260 getemail( curto, eto )
261 data = "MAIL FROM: <\(efrom)>
262 RCPT TO: <\(eto)>
263 DATA
264 From: \(from)
265 To: \(curto)
266 Subject: \(subject)
267 Date: \(date)
268 Mime-Version: 1.0
269 \( ?( *exthead, "\(exthead)\l", "" ))
270 \(body)
271 .
272 "
273 uint off sent remain = *data
274
275 while remain
276 {
277 sent = send( this.sock.socket, data.ptr() + off, min( 0x7FFF, remain ), 0 )
278 if sent == $SOCKET_ERROR
279 {
280 inet_seterror()
281 this.sock.close()
282 goto close
283 }
284 else
285 {
286 this.ni.param += sent
287 if !this.notify( $NFYINET_PUT )
288 {
289 this.sock.close()
290 goto close
291 }
292 }
293 remain -= sent
294 off += sent
295 }
296 if !.cmdresponse() : return 0
297 }
298 return 1
299 label close
300 this.notify( $NFYINET_ERROR )
301 return 0
302 }
303