Mercurial > pidgin.yaz
annotate src/protocols/oscar/service.c @ 5842:dbed8c87f750
[gaim-migrate @ 6273]
Another reason not to use CVS.
This saves your buddy icons to the OSCAR servers (I hope). It's not fully
tested and it may screw things up, but I wanted to get it in CVS so other people
can try to break this, and KingAnt can review my work (that libfaim wizard that
he is.) Don't use CVS.
I'd like to thank Christian Hammond, for his work on the account and prpl APIs
that made it so easy to add the hook into buddy icon changing. I'd like to thank
Mark Doliner for laying most of the groundwork and for his kind words of encouragement.
I'd like to thank myself for making the buddy icon selector real sexy-like. I'd like
to thank the Lord almighty for giving me the strength to do this.
I promised myself I wouldn't cry. I never imagined I'd be committing this.
committer: Tailor Script <tailor@pidgin.im>
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Thu, 12 Jun 2003 22:19:07 +0000 |
parents | a48c338dff6c |
children | ec963ca8c9ee |
rev | line source |
---|---|
2703 | 1 /* |
3952 | 2 * Family 0x0001 - This is a very special group. All connections support |
2703 | 3 * this group, as it does some particularly good things (like rate limiting). |
4 */ | |
5 | |
6 #define FAIM_INTERNAL | |
2734
9fc65bb80596
[gaim-migrate @ 2747]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2703
diff
changeset
|
7 #define FAIM_NEED_CONN_INTERNAL |
2703 | 8 #include <aim.h> |
9 | |
10 #include "md5.h" | |
11 | |
3952 | 12 /* Subtype 0x0002 - Client Online */ |
2703 | 13 faim_export int aim_clientready(aim_session_t *sess, aim_conn_t *conn) |
14 { | |
15 aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; | |
16 struct snacgroup *sg; | |
17 aim_frame_t *fr; | |
18 aim_snacid_t snacid; | |
19 | |
20 if (!ins) | |
21 return -EINVAL; | |
22 | |
23 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) | |
24 return -ENOMEM; | |
25 | |
26 snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0); | |
27 aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid); | |
28 | |
29 /* | |
30 * Send only the tool versions that the server cares about (that it | |
31 * marked as supporting in the server ready SNAC). | |
32 */ | |
33 for (sg = ins->groups; sg; sg = sg->next) { | |
34 aim_module_t *mod; | |
35 | |
36 if ((mod = aim__findmodulebygroup(sess, sg->group))) { | |
37 aimbs_put16(&fr->data, mod->family); | |
38 aimbs_put16(&fr->data, mod->version); | |
39 aimbs_put16(&fr->data, mod->toolid); | |
40 aimbs_put16(&fr->data, mod->toolversion); | |
41 } else | |
42 faimdprintf(sess, 1, "aim_clientready: server supports group 0x%04x but we don't!\n", sg->group); | |
43 } | |
44 | |
45 aim_tx_enqueue(sess, fr); | |
46 | |
47 return 0; | |
48 } | |
49 | |
50 /* | |
3952 | 51 * Subtype 0x0003 - Host Online |
2703 | 52 * |
53 * See comments in conn.c about how the group associations are supposed | |
54 * to work, and how they really work. | |
55 * | |
56 * This info probably doesn't even need to make it to the client. | |
57 * | |
58 * We don't actually call the client here. This starts off the connection | |
59 * initialization routine required by all AIM connections. The next time | |
60 * the client is called is the CONNINITDONE callback, which should be | |
61 * shortly after the rate information is acknowledged. | |
62 * | |
63 */ | |
64 static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) | |
65 { | |
66 fu16_t *families; | |
67 int famcount; | |
68 | |
69 | |
70 if (!(families = malloc(aim_bstream_empty(bs)))) | |
71 return 0; | |
72 | |
73 for (famcount = 0; aim_bstream_empty(bs); famcount++) { | |
74 families[famcount] = aimbs_get16(bs); | |
75 aim_conn_addgroup(rx->conn, families[famcount]); | |
76 } | |
77 | |
78 free(families); | |
79 | |
80 | |
81 /* | |
82 * Next step is in the Host Versions handler. | |
83 * | |
84 * Note that we must send this before we request rates, since | |
85 * the format of the rate information depends on the versions we | |
86 * give it. | |
87 * | |
88 */ | |
89 aim_setversions(sess, rx->conn); | |
90 | |
91 return 1; | |
92 } | |
93 | |
3952 | 94 /* Subtype 0x0004 - Service request */ |
2703 | 95 faim_export int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, fu16_t serviceid) |
96 { | |
97 return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid); | |
98 } | |
99 | |
3952 | 100 /* Subtype 0x0005 - Redirect */ |
2703 | 101 static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
102 { | |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
103 struct aim_redirect_data redir; |
2703 | 104 aim_rxcallback_t userfunc; |
105 aim_tlvlist_t *tlvlist; | |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
106 aim_snac_t *origsnac = NULL; |
2703 | 107 int ret = 0; |
108 | |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
109 memset(&redir, 0, sizeof(redir)); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
110 |
2703 | 111 tlvlist = aim_readtlvchain(bs); |
112 | |
113 if (!aim_gettlv(tlvlist, 0x000d, 1) || | |
114 !aim_gettlv(tlvlist, 0x0005, 1) || | |
115 !aim_gettlv(tlvlist, 0x0006, 1)) { | |
116 aim_freetlvchain(&tlvlist); | |
117 return 0; | |
118 } | |
119 | |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
120 redir.group = aim_gettlv16(tlvlist, 0x000d, 1); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
121 redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1); |
4293 | 122 redir.cookielen = aim_gettlv(tlvlist, 0x0006, 1)->length; |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
123 redir.cookie = aim_gettlv_str(tlvlist, 0x0006, 1); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
124 |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
125 /* Fetch original SNAC so we can get csi if needed */ |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
126 origsnac = aim_remsnac(sess, snac->id); |
2703 | 127 |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
128 if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) { |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
129 struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data; |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
130 |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
131 redir.chat.exchange = csi->exchange; |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
132 redir.chat.room = csi->name; |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
133 redir.chat.instance = csi->instance; |
2703 | 134 } |
135 | |
136 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
137 ret = userfunc(sess, rx, &redir); |
2703 | 138 |
2821
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
139 free((void *)redir.ip); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
140 free((void *)redir.cookie); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
141 |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
142 if (origsnac) |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
143 free(origsnac->data); |
9467e4ee81be
[gaim-migrate @ 2834]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2734
diff
changeset
|
144 free(origsnac); |
2703 | 145 |
146 aim_freetlvchain(&tlvlist); | |
147 | |
148 return ret; | |
149 } | |
150 | |
3952 | 151 /* Subtype 0x0006 - Request Rate Information. */ |
2703 | 152 faim_internal int aim_reqrates(aim_session_t *sess, aim_conn_t *conn) |
153 { | |
154 return aim_genericreq_n(sess, conn, 0x0001, 0x0006); | |
155 } | |
156 | |
157 /* | |
158 * OSCAR defines several 'rate classes'. Each class has seperate | |
159 * rate limiting properties (limit level, alert level, disconnect | |
160 * level, etc), and a set of SNAC family/type pairs associated with | |
161 * it. The rate classes, their limiting properties, and the definitions | |
162 * of which SNACs are belong to which class, are defined in the | |
163 * Rate Response packet at login to each host. | |
164 * | |
165 * Logically, all rate offenses within one class count against further | |
166 * offenses for other SNACs in the same class (ie, sending messages | |
167 * too fast will limit the number of user info requests you can send, | |
168 * since those two SNACs are in the same rate class). | |
169 * | |
170 * Since the rate classes are defined dynamically at login, the values | |
171 * below may change. But they seem to be fairly constant. | |
172 * | |
173 * Currently, BOS defines five rate classes, with the commonly used | |
174 * members as follows... | |
175 * | |
176 * Rate class 0x0001: | |
177 * - Everything thats not in any of the other classes | |
178 * | |
179 * Rate class 0x0002: | |
180 * - Buddy list add/remove | |
181 * - Permit list add/remove | |
182 * - Deny list add/remove | |
183 * | |
184 * Rate class 0x0003: | |
185 * - User information requests | |
186 * - Outgoing ICBMs | |
187 * | |
188 * Rate class 0x0004: | |
189 * - A few unknowns: 2/9, 2/b, and f/2 | |
190 * | |
191 * Rate class 0x0005: | |
192 * - Chat room create | |
193 * - Outgoing chat ICBMs | |
194 * | |
195 * The only other thing of note is that class 5 (chat) has slightly looser | |
196 * limiting properties than class 3 (normal messages). But thats just a | |
197 * small bit of trivia for you. | |
198 * | |
199 * The last thing that needs to be learned about the rate limiting | |
200 * system is how the actual numbers relate to the passing of time. This | |
201 * seems to be a big mystery. | |
202 * | |
203 */ | |
204 | |
205 static void rc_addclass(struct rateclass **head, struct rateclass *inrc) | |
206 { | |
207 struct rateclass *rc, *rc2; | |
208 | |
209 if (!(rc = malloc(sizeof(struct rateclass)))) | |
210 return; | |
211 | |
212 memcpy(rc, inrc, sizeof(struct rateclass)); | |
213 rc->next = NULL; | |
214 | |
215 for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next) | |
216 ; | |
217 | |
218 if (!rc2) | |
219 *head = rc; | |
220 else | |
221 rc2->next = rc; | |
222 | |
223 return; | |
224 } | |
225 | |
226 static struct rateclass *rc_findclass(struct rateclass **head, fu16_t id) | |
227 { | |
228 struct rateclass *rc; | |
229 | |
230 for (rc = *head; rc; rc = rc->next) { | |
231 if (rc->classid == id) | |
232 return rc; | |
233 } | |
234 | |
235 return NULL; | |
236 } | |
237 | |
238 static void rc_addpair(struct rateclass *rc, fu16_t group, fu16_t type) | |
239 { | |
240 struct snacpair *sp, *sp2; | |
241 | |
242 if (!(sp = malloc(sizeof(struct snacpair)))) | |
243 return; | |
244 memset(sp, 0, sizeof(struct snacpair)); | |
245 | |
246 sp->group = group; | |
247 sp->subtype = type; | |
248 sp->next = NULL; | |
249 | |
250 for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next) | |
251 ; | |
252 | |
253 if (!sp2) | |
254 rc->members = sp; | |
255 else | |
256 sp2->next = sp; | |
257 | |
258 return; | |
259 } | |
260 | |
3952 | 261 /* Subtype 0x0007 - Rate Parameters */ |
2703 | 262 static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
263 { | |
264 aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside; | |
265 fu16_t numclasses, i; | |
266 aim_rxcallback_t userfunc; | |
267 | |
268 | |
269 /* | |
270 * First are the parameters for each rate class. | |
271 */ | |
272 numclasses = aimbs_get16(bs); | |
273 for (i = 0; i < numclasses; i++) { | |
274 struct rateclass rc; | |
275 | |
276 memset(&rc, 0, sizeof(struct rateclass)); | |
277 | |
278 rc.classid = aimbs_get16(bs); | |
279 rc.windowsize = aimbs_get32(bs); | |
280 rc.clear = aimbs_get32(bs); | |
281 rc.alert = aimbs_get32(bs); | |
282 rc.limit = aimbs_get32(bs); | |
283 rc.disconnect = aimbs_get32(bs); | |
284 rc.current = aimbs_get32(bs); | |
285 rc.max = aimbs_get32(bs); | |
286 | |
287 /* | |
288 * The server will send an extra five bytes of parameters | |
289 * depending on the version we advertised in 1/17. If we | |
290 * didn't send 1/17 (evil!), then this will crash and you | |
291 * die, as it will default to the old version but we have | |
292 * the new version hardcoded here. | |
293 */ | |
294 if (mod->version >= 3) | |
295 aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown)); | |
296 | |
2734
9fc65bb80596
[gaim-migrate @ 2747]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2703
diff
changeset
|
297 faimdprintf(sess, 1, "--- Adding rate class %d to connection type %d: window size = %ld, clear = %ld, alert = %ld, limit = %ld, disconnect = %ld, current = %ld, max = %ld\n", rx->conn->type, rc.classid, rc.windowsize, rc.clear, rc.alert, rc.limit, rc.disconnect, rc.current, rc.max); |
9fc65bb80596
[gaim-migrate @ 2747]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
2703
diff
changeset
|
298 |
2703 | 299 rc_addclass(&ins->rates, &rc); |
300 } | |
301 | |
302 /* | |
303 * Then the members of each class. | |
304 */ | |
305 for (i = 0; i < numclasses; i++) { | |
306 fu16_t classid, count; | |
307 struct rateclass *rc; | |
308 int j; | |
309 | |
310 classid = aimbs_get16(bs); | |
311 count = aimbs_get16(bs); | |
312 | |
313 rc = rc_findclass(&ins->rates, classid); | |
314 | |
315 for (j = 0; j < count; j++) { | |
316 fu16_t group, subtype; | |
317 | |
318 group = aimbs_get16(bs); | |
319 subtype = aimbs_get16(bs); | |
320 | |
321 if (rc) | |
322 rc_addpair(rc, group, subtype); | |
323 } | |
324 } | |
325 | |
326 /* | |
327 * We don't pass the rate information up to the client, as it really | |
328 * doesn't care. The information is stored in the connection, however | |
329 * so that we can do more fun stuff later (not really). | |
330 */ | |
331 | |
332 /* | |
333 * Last step in the conn init procedure is to acknowledge that we | |
334 * agree to these draconian limitations. | |
335 */ | |
336 aim_rates_addparam(sess, rx->conn); | |
337 | |
338 /* | |
339 * Finally, tell the client it's ready to go... | |
340 */ | |
341 if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE))) | |
342 userfunc(sess, rx); | |
343 | |
344 | |
345 return 1; | |
346 } | |
347 | |
3952 | 348 /* Subtype 0x0008 - Add Rate Parameter */ |
2703 | 349 faim_internal int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn) |
350 { | |
351 aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; | |
352 aim_frame_t *fr; | |
353 aim_snacid_t snacid; | |
354 struct rateclass *rc; | |
355 | |
356 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) | |
357 return -ENOMEM; | |
358 | |
359 snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0); | |
360 aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid); | |
361 | |
362 for (rc = ins->rates; rc; rc = rc->next) | |
363 aimbs_put16(&fr->data, rc->classid); | |
364 | |
365 aim_tx_enqueue(sess, fr); | |
366 | |
367 return 0; | |
368 } | |
369 | |
3952 | 370 /* Subtype 0x0009 - Delete Rate Parameter */ |
2703 | 371 faim_internal int aim_rates_delparam(aim_session_t *sess, aim_conn_t *conn) |
372 { | |
373 aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; | |
374 aim_frame_t *fr; | |
375 aim_snacid_t snacid; | |
376 struct rateclass *rc; | |
377 | |
378 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) | |
379 return -ENOMEM; | |
380 | |
381 snacid = aim_cachesnac(sess, 0x0001, 0x0009, 0x0000, NULL, 0); | |
382 aim_putsnac(&fr->data, 0x0001, 0x0009, 0x0000, snacid); | |
383 | |
384 for (rc = ins->rates; rc; rc = rc->next) | |
385 aimbs_put16(&fr->data, rc->classid); | |
386 | |
387 aim_tx_enqueue(sess, fr); | |
388 | |
389 return 0; | |
390 } | |
391 | |
3952 | 392 /* Subtype 0x000a - Rate Change */ |
2703 | 393 static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
394 { | |
4871 | 395 int ret = 0; |
2703 | 396 aim_rxcallback_t userfunc; |
397 fu16_t code, rateclass; | |
398 fu32_t currentavg, maxavg, windowsize, clear, alert, limit, disconnect; | |
399 | |
400 code = aimbs_get16(bs); | |
401 rateclass = aimbs_get16(bs); | |
402 | |
403 windowsize = aimbs_get32(bs); | |
404 clear = aimbs_get32(bs); | |
405 alert = aimbs_get32(bs); | |
406 limit = aimbs_get32(bs); | |
407 disconnect = aimbs_get32(bs); | |
408 currentavg = aimbs_get32(bs); | |
409 maxavg = aimbs_get32(bs); | |
410 | |
411 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 412 ret = userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg); |
2703 | 413 |
4871 | 414 return ret; |
2703 | 415 } |
416 | |
417 /* | |
3952 | 418 * How Migrations work. |
2703 | 419 * |
420 * The server sends a Server Pause message, which the client should respond to | |
421 * with a Server Pause Ack, which contains the families it needs on this | |
422 * connection. The server will send a Migration Notice with an IP address, and | |
423 * then disconnect. Next the client should open the connection and send the | |
424 * cookie. Repeat the normal login process and pretend this never happened. | |
425 * | |
426 * The Server Pause contains no data. | |
427 * | |
428 */ | |
429 | |
3952 | 430 /* Subtype 0x000b - Service Pause */ |
2703 | 431 static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
432 { | |
4871 | 433 int ret = 0; |
2703 | 434 aim_rxcallback_t userfunc; |
435 | |
436 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 437 ret = userfunc(sess, rx); |
2703 | 438 |
4871 | 439 return ret; |
2703 | 440 } |
441 | |
442 /* | |
3952 | 443 * Subtype 0x000c - Service Pause Acknowledgement |
2703 | 444 * |
445 * It is rather important that aim_sendpauseack() gets called for the exact | |
446 * same connection that the Server Pause callback was called for, since | |
447 * libfaim extracts the data for the SNAC from the connection structure. | |
448 * | |
449 * Of course, if you don't do that, more bad things happen than just what | |
450 * libfaim can cause. | |
451 * | |
452 */ | |
453 faim_export int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn) | |
454 { | |
455 aim_frame_t *fr; | |
456 aim_snacid_t snacid; | |
457 aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; | |
458 struct snacgroup *sg; | |
459 | |
460 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1024))) | |
461 return -ENOMEM; | |
462 | |
463 snacid = aim_cachesnac(sess, 0x0001, 0x000c, 0x0000, NULL, 0); | |
464 aim_putsnac(&fr->data, 0x0001, 0x000c, 0x0000, snacid); | |
465 | |
466 /* | |
467 * This list should have all the groups that the original | |
468 * Host Online / Server Ready said this host supports. And | |
469 * we want them all back after the migration. | |
470 */ | |
471 for (sg = ins->groups; sg; sg = sg->next) | |
472 aimbs_put16(&fr->data, sg->group); | |
473 | |
474 aim_tx_enqueue(sess, fr); | |
475 | |
476 return 0; | |
477 } | |
478 | |
3952 | 479 /* Subtype 0x000d - Service Resume */ |
2703 | 480 static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
481 { | |
4871 | 482 int ret = 0; |
2703 | 483 aim_rxcallback_t userfunc; |
484 | |
485 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 486 ret = userfunc(sess, rx); |
2703 | 487 |
4871 | 488 return ret; |
2703 | 489 } |
490 | |
3952 | 491 /* Subtype 0x000e - Request self-info */ |
2703 | 492 faim_export int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn) |
493 { | |
494 return aim_genericreq_n(sess, conn, 0x0001, 0x000e); | |
495 } | |
496 | |
3952 | 497 /* Subtype 0x000f - Self User Info */ |
2703 | 498 static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
499 { | |
4871 | 500 int ret = 0; |
2703 | 501 aim_rxcallback_t userfunc; |
502 aim_userinfo_t userinfo; | |
503 | |
5836 | 504 aim_info_extract(sess, bs, &userinfo); |
2703 | 505 |
506 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 507 ret = userfunc(sess, rx, &userinfo); |
2703 | 508 |
5836 | 509 aim_info_free(&userinfo); |
510 | |
4871 | 511 return ret; |
2703 | 512 } |
513 | |
3952 | 514 /* Subtype 0x0010 - Evil Notification */ |
2703 | 515 static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
516 { | |
4871 | 517 int ret = 0; |
2703 | 518 aim_rxcallback_t userfunc; |
519 fu16_t newevil; | |
520 aim_userinfo_t userinfo; | |
521 | |
522 memset(&userinfo, 0, sizeof(aim_userinfo_t)); | |
523 | |
524 newevil = aimbs_get16(bs); | |
525 | |
526 if (aim_bstream_empty(bs)) | |
5836 | 527 aim_info_extract(sess, bs, &userinfo); |
2703 | 528 |
529 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 530 ret = userfunc(sess, rx, newevil, &userinfo); |
2703 | 531 |
5836 | 532 aim_info_free(&userinfo); |
533 | |
4871 | 534 return ret; |
2703 | 535 } |
536 | |
537 /* | |
3952 | 538 * Subtype 0x0011 - Idle Notification |
2703 | 539 * |
540 * Should set your current idle time in seconds. Note that this should | |
541 * never be called consecutively with a non-zero idle time. That makes | |
542 * OSCAR do funny things. Instead, just set it once you go idle, and then | |
543 * call it again with zero when you're back. | |
544 * | |
545 */ | |
546 faim_export int aim_bos_setidle(aim_session_t *sess, aim_conn_t *conn, fu32_t idletime) | |
547 { | |
548 return aim_genericreq_l(sess, conn, 0x0001, 0x0011, &idletime); | |
549 } | |
550 | |
551 /* | |
3952 | 552 * Subtype 0x0012 - Service Migrate |
2703 | 553 * |
554 * This is the final SNAC sent on the original connection during a migration. | |
555 * It contains the IP and cookie used to connect to the new server, and | |
556 * optionally a list of the SNAC groups being migrated. | |
557 * | |
558 */ | |
559 static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) | |
560 { | |
561 aim_rxcallback_t userfunc; | |
562 int ret = 0; | |
563 fu16_t groupcount, i; | |
564 aim_tlvlist_t *tl; | |
565 char *ip = NULL; | |
566 aim_tlv_t *cktlv; | |
567 | |
568 /* | |
569 * Apparently there's some fun stuff that can happen right here. The | |
570 * migration can actually be quite selective about what groups it | |
571 * moves to the new server. When not all the groups for a connection | |
572 * are migrated, or they are all migrated but some groups are moved | |
573 * to a different server than others, it is called a bifurcated | |
574 * migration. | |
575 * | |
576 * Let's play dumb and not support that. | |
577 * | |
578 */ | |
579 groupcount = aimbs_get16(bs); | |
580 for (i = 0; i < groupcount; i++) { | |
581 fu16_t group; | |
582 | |
583 group = aimbs_get16(bs); | |
584 | |
585 faimdprintf(sess, 0, "bifurcated migration unsupported -- group 0x%04x\n", group); | |
586 } | |
587 | |
588 tl = aim_readtlvchain(bs); | |
589 | |
590 if (aim_gettlv(tl, 0x0005, 1)) | |
591 ip = aim_gettlv_str(tl, 0x0005, 1); | |
592 | |
593 cktlv = aim_gettlv(tl, 0x0006, 1); | |
594 | |
595 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
596 ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL); | |
597 | |
598 aim_freetlvchain(&tl); | |
599 free(ip); | |
600 | |
601 return ret; | |
602 } | |
603 | |
3952 | 604 /* Subtype 0x0013 - Message of the Day */ |
2703 | 605 static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
606 { | |
607 aim_rxcallback_t userfunc; | |
608 char *msg = NULL; | |
609 int ret = 0; | |
610 aim_tlvlist_t *tlvlist; | |
611 fu16_t id; | |
612 | |
613 /* | |
614 * Code. | |
615 * | |
616 * Valid values: | |
617 * 1 Mandatory upgrade | |
618 * 2 Advisory upgrade | |
619 * 3 System bulletin | |
620 * 4 Nothing's wrong ("top o the world" -- normal) | |
621 * 5 Lets-break-something. | |
622 * | |
623 */ | |
624 id = aimbs_get16(bs); | |
625 | |
626 /* | |
627 * TLVs follow | |
628 */ | |
629 tlvlist = aim_readtlvchain(bs); | |
630 | |
631 msg = aim_gettlv_str(tlvlist, 0x000b, 1); | |
632 | |
633 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
634 ret = userfunc(sess, rx, id, msg); | |
635 | |
636 free(msg); | |
637 | |
638 aim_freetlvchain(&tlvlist); | |
639 | |
640 return ret; | |
641 } | |
642 | |
643 /* | |
3952 | 644 * Subtype 0x0014 - Set privacy flags |
2703 | 645 * |
646 * Normally 0x03. | |
647 * | |
648 * Bit 1: Allows other AIM users to see how long you've been idle. | |
649 * Bit 2: Allows other AIM users to see how long you've been a member. | |
650 * | |
651 */ | |
652 faim_export int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, fu32_t flags) | |
653 { | |
654 return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags); | |
655 } | |
656 | |
657 /* | |
3952 | 658 * Subtype 0x0016 - No-op |
2703 | 659 * |
660 * WinAIM sends these every 4min or so to keep the connection alive. Its not | |
3952 | 661 * really necessary. |
2703 | 662 * |
663 */ | |
664 faim_export int aim_nop(aim_session_t *sess, aim_conn_t *conn) | |
665 { | |
666 return aim_genericreq_n(sess, conn, 0x0001, 0x0016); | |
667 } | |
668 | |
669 /* | |
3952 | 670 * Subtype 0x0017 - Set client versions |
2703 | 671 * |
672 * If you've seen the clientonline/clientready SNAC you're probably | |
673 * wondering what the point of this one is. And that point seems to be | |
674 * that the versions in the client online SNAC are sent too late for the | |
675 * server to be able to use them to change the protocol for the earlier | |
676 * login packets (client versions are sent right after Host Online is | |
677 * received, but client online versions aren't sent until quite a bit later). | |
678 * We can see them already making use of this by changing the format of | |
679 * the rate information based on what version of group 1 we advertise here. | |
680 * | |
681 */ | |
682 faim_internal int aim_setversions(aim_session_t *sess, aim_conn_t *conn) | |
683 { | |
684 aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; | |
685 struct snacgroup *sg; | |
686 aim_frame_t *fr; | |
687 aim_snacid_t snacid; | |
688 | |
689 if (!ins) | |
690 return -EINVAL; | |
691 | |
692 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) | |
693 return -ENOMEM; | |
694 | |
695 snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0); | |
696 aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid); | |
697 | |
698 /* | |
699 * Send only the versions that the server cares about (that it | |
700 * marked as supporting in the server ready SNAC). | |
701 */ | |
702 for (sg = ins->groups; sg; sg = sg->next) { | |
703 aim_module_t *mod; | |
704 | |
705 if ((mod = aim__findmodulebygroup(sess, sg->group))) { | |
706 aimbs_put16(&fr->data, mod->family); | |
707 aimbs_put16(&fr->data, mod->version); | |
708 } else | |
709 faimdprintf(sess, 1, "aim_setversions: server supports group 0x%04x but we don't!\n", sg->group); | |
710 } | |
711 | |
712 aim_tx_enqueue(sess, fr); | |
713 | |
714 return 0; | |
715 } | |
716 | |
3952 | 717 /* Subtype 0x0018 - Host versions */ |
2703 | 718 static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
719 { | |
720 int vercount; | |
721 fu8_t *versions; | |
722 | |
723 /* This is frivolous. (Thank you SmarterChild.) */ | |
724 vercount = aim_bstream_empty(bs)/4; | |
725 versions = aimbs_getraw(bs, aim_bstream_empty(bs)); | |
726 free(versions); | |
727 | |
728 /* | |
729 * Now request rates. | |
730 */ | |
731 aim_reqrates(sess, rx->conn); | |
732 | |
733 return 1; | |
734 } | |
735 | |
736 /* | |
3952 | 737 * Subtype 0x001e - Set Extended Status |
2703 | 738 * |
739 * Currently only works if using ICQ. | |
740 * | |
4342 | 741 * These are the same TLVs seen in user info. You can |
742 * also set 0x0008 and 0x000c. | |
2703 | 743 */ |
4901 | 744 faim_export int aim_setextstatus(aim_session_t *sess, fu32_t status) |
2703 | 745 { |
4901 | 746 aim_conn_t *conn; |
2703 | 747 aim_frame_t *fr; |
748 aim_snacid_t snacid; | |
749 aim_tlvlist_t *tl = NULL; | |
750 fu32_t data; | |
751 | |
4901 | 752 if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) |
753 return -EINVAL; | |
754 | |
4342 | 755 data = AIM_ICQ_STATE_HIDEIP | AIM_ICQ_STATE_WEBAWARE | status; /* yay for error checking ;^) */ |
2703 | 756 |
757 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8))) | |
758 return -ENOMEM; | |
759 | |
760 snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0); | |
761 aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid); | |
762 | |
763 aim_addtlvtochain32(&tl, 0x0006, data); | |
764 aim_writetlvchain(&fr->data, &tl); | |
765 aim_freetlvchain(&tl); | |
766 | |
767 aim_tx_enqueue(sess, fr); | |
768 | |
769 return 0; | |
770 } | |
771 | |
772 /* | |
773 * Starting this past week (26 Mar 2001, say), AOL has started sending | |
774 * this nice little extra SNAC. AFAIK, it has never been used until now. | |
775 * | |
776 * The request contains eight bytes. The first four are an offset, the | |
777 * second four are a length. | |
778 * | |
779 * The offset is an offset into aim.exe when it is mapped during execution | |
780 * on Win32. So far, AOL has only been requesting bytes in static regions | |
781 * of memory. (I won't put it past them to start requesting data in | |
782 * less static regions -- regions that are initialized at run time, but still | |
783 * before the client recieves this request.) | |
784 * | |
785 * When the client recieves the request, it adds it to the current ds | |
786 * (0x00400000) and dereferences it, copying the data into a buffer which | |
787 * it then runs directly through the MD5 hasher. The 16 byte output of | |
788 * the hash is then sent back to the server. | |
789 * | |
790 * If the client does not send any data back, or the data does not match | |
791 * the data that the specific client should have, the client will get the | |
792 * following message from "AOL Instant Messenger": | |
793 * "You have been disconnected from the AOL Instant Message Service (SM) | |
794 * for accessing the AOL network using unauthorized software. You can | |
795 * download a FREE, fully featured, and authorized client, here | |
796 * http://www.aol.com/aim/download2.html" | |
797 * The connection is then closed, recieving disconnect code 1, URL | |
798 * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html. | |
799 * | |
800 * Note, however, that numerous inconsistencies can cause the above error, | |
801 * not just sending back a bad hash. Do not immediatly suspect this code | |
802 * if you get disconnected. AOL and the open/free software community have | |
803 * played this game for a couple years now, generating the above message | |
804 * on numerous ocassions. | |
805 * | |
806 * Anyway, neener. We win again. | |
807 * | |
808 */ | |
3952 | 809 /* Subtype 0x001f - Client verification */ |
2703 | 810 static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
811 { | |
4871 | 812 int ret = 0; |
2703 | 813 aim_rxcallback_t userfunc; |
814 fu32_t offset, len; | |
815 aim_tlvlist_t *list; | |
816 char *modname; | |
817 | |
818 offset = aimbs_get32(bs); | |
819 len = aimbs_get32(bs); | |
820 list = aim_readtlvchain(bs); | |
821 | |
822 modname = aim_gettlv_str(list, 0x0001, 1); | |
823 | |
824 faimdprintf(sess, 1, "data at 0x%08lx (%d bytes) of requested\n", offset, len, modname ? modname : "aim.exe"); | |
825 | |
826 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) | |
4871 | 827 ret = userfunc(sess, rx, offset, len, modname); |
2703 | 828 |
829 free(modname); | |
830 aim_freetlvchain(&list); | |
831 | |
4871 | 832 return ret; |
2703 | 833 } |
834 | |
835 #if 0 | |
836 static void dumpbox(aim_session_t *sess, unsigned char *buf, int len) | |
837 { | |
838 int i; | |
839 | |
840 if (!sess || !buf || !len) | |
841 return; | |
842 | |
843 faimdprintf(sess, 1, "\nDump of %d bytes at %p:", len, buf); | |
844 | |
845 for (i = 0; i < len; i++) { | |
846 if ((i % 8) == 0) | |
847 faimdprintf(sess, 1, "\n\t"); | |
848 | |
849 faimdprintf(sess, 1, "0x%2x ", buf[i]); | |
850 } | |
851 | |
852 faimdprintf(sess, 1, "\n\n"); | |
853 | |
854 return; | |
855 } | |
856 #endif | |
857 | |
3952 | 858 /* Subtype 0x0020 - Client verification reply */ |
2703 | 859 faim_export int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, fu32_t offset, fu32_t len, const fu8_t *buf, fu8_t flag) |
860 { | |
861 aim_frame_t *fr; | |
862 aim_snacid_t snacid; | |
863 | |
864 if (!sess || !conn) | |
865 return -EINVAL; | |
866 | |
867 if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+16))) | |
868 return -ENOMEM; | |
869 | |
870 snacid = aim_cachesnac(sess, 0x0001, 0x0020, 0x0000, NULL, 0); | |
871 | |
872 aim_putsnac(&fr->data, 0x0001, 0x0020, 0x0000, snacid); | |
873 aimbs_put16(&fr->data, 0x0010); /* md5 is always 16 bytes */ | |
874 | |
875 if ((flag == AIM_SENDMEMBLOCK_FLAG_ISHASH) && buf && (len == 0x10)) { /* we're getting a hash */ | |
876 | |
877 aimbs_putraw(&fr->data, buf, 0x10); | |
878 | |
879 } else if (buf && (len > 0)) { /* use input buffer */ | |
880 md5_state_t state; | |
881 md5_byte_t digest[0x10]; | |
882 | |
883 md5_init(&state); | |
884 md5_append(&state, (const md5_byte_t *)buf, len); | |
885 md5_finish(&state, digest); | |
886 | |
887 aimbs_putraw(&fr->data, (fu8_t *)digest, 0x10); | |
888 | |
889 } else if (len == 0) { /* no length, just hash NULL (buf is optional) */ | |
890 md5_state_t state; | |
891 fu8_t nil = '\0'; | |
892 md5_byte_t digest[0x10]; | |
893 | |
894 /* | |
895 * These MD5 routines are stupid in that you have to have | |
896 * at least one append. So thats why this doesn't look | |
897 * real logical. | |
898 */ | |
899 md5_init(&state); | |
900 md5_append(&state, (const md5_byte_t *)&nil, 0); | |
901 md5_finish(&state, digest); | |
902 | |
903 aimbs_putraw(&fr->data, (fu8_t *)digest, 0x10); | |
904 | |
905 } else { | |
906 | |
907 /* | |
908 * This data is correct for AIM 3.5.1670. | |
909 * | |
910 * Using these blocks is as close to "legal" as you can get | |
911 * without using an AIM binary. | |
912 * | |
913 */ | |
914 if ((offset == 0x03ffffff) && (len == 0x03ffffff)) { | |
915 | |
916 #if 1 /* with "AnrbnrAqhfzcd" */ | |
917 aimbs_put32(&fr->data, 0x44a95d26); | |
918 aimbs_put32(&fr->data, 0xd2490423); | |
919 aimbs_put32(&fr->data, 0x93b8821f); | |
920 aimbs_put32(&fr->data, 0x51c54b01); | |
921 #else /* no filename */ | |
922 aimbs_put32(&fr->data, 0x1df8cbae); | |
923 aimbs_put32(&fr->data, 0x5523b839); | |
924 aimbs_put32(&fr->data, 0xa0e10db3); | |
925 aimbs_put32(&fr->data, 0xa46d3b39); | |
926 #endif | |
927 | |
928 } else if ((offset == 0x00001000) && (len == 0x00000000)) { | |
929 | |
930 aimbs_put32(&fr->data, 0xd41d8cd9); | |
931 aimbs_put32(&fr->data, 0x8f00b204); | |
932 aimbs_put32(&fr->data, 0xe9800998); | |
933 aimbs_put32(&fr->data, 0xecf8427e); | |
934 | |
935 } else | |
936 faimdprintf(sess, 0, "sendmemblock: WARNING: unknown hash request\n"); | |
937 | |
938 } | |
939 | |
940 aim_tx_enqueue(sess, fr); | |
941 | |
942 return 0; | |
943 } | |
944 | |
5836 | 945 /* |
946 * Subtype 0x0021 - Receive our extended status | |
947 * | |
948 * This is used for MAC non-away "away" messages, and maybe ICQ extended status messages? | |
5842 | 949 * It's also used to tell the client whether or not it needs to upload an SSI buddy icon... who engineers this stuff, anyway? |
5836 | 950 */ |
951 static int aim_parse_extstatus(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) | |
952 { | |
5842 | 953 int ret = 0, i; |
5836 | 954 aim_rxcallback_t userfunc; |
5842 | 955 char *msg = NULL, *md5 = NULL; |
5837 | 956 fu16_t type; |
5842 | 957 fu8_t number, length, cached; |
5836 | 958 |
5842 | 959 type = aimbs_get16(bs); |
960 printf("blah: %d\n", type); | |
961 if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { | |
962 switch (type) { | |
963 case 0x0001: | |
964 case 0x0000: /* not sure what the difference between 1 and 0 is */ | |
965 cached = aimbs_get8(bs); | |
966 length = aimbs_get8(bs); | |
967 md5 = aimbs_getraw(bs, length); | |
968 ret = userfunc(sess, rx, type, cached, length, md5); | |
969 free(md5); | |
970 break; | |
971 case 0x0002: | |
972 number = aimbs_get8(bs); /* 0x04 */ | |
973 length = aimbs_get8(bs); /* the first length */ | |
974 msg = aimbs_getstr(bs, aimbs_get16(bs)); /* the second length is just for the message */ | |
975 ret = userfunc(sess, rx, msg); | |
976 free(msg); | |
977 break; | |
978 } | |
979 return ret; | |
980 } | |
5836 | 981 } |
982 | |
2703 | 983 static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) |
984 { | |
985 | |
986 if (snac->subtype == 0x0003) | |
987 return hostonline(sess, mod, rx, snac, bs); | |
988 else if (snac->subtype == 0x0005) | |
989 return redirect(sess, mod, rx, snac, bs); | |
990 else if (snac->subtype == 0x0007) | |
991 return rateresp(sess, mod, rx, snac, bs); | |
992 else if (snac->subtype == 0x000a) | |
993 return ratechange(sess, mod, rx, snac, bs); | |
994 else if (snac->subtype == 0x000b) | |
995 return serverpause(sess, mod, rx, snac, bs); | |
996 else if (snac->subtype == 0x000d) | |
997 return serverresume(sess, mod, rx, snac, bs); | |
998 else if (snac->subtype == 0x000f) | |
999 return selfinfo(sess, mod, rx, snac, bs); | |
1000 else if (snac->subtype == 0x0010) | |
1001 return evilnotify(sess, mod, rx, snac, bs); | |
1002 else if (snac->subtype == 0x0012) | |
1003 return migrate(sess, mod, rx, snac, bs); | |
1004 else if (snac->subtype == 0x0013) | |
1005 return motd(sess, mod, rx, snac, bs); | |
1006 else if (snac->subtype == 0x0018) | |
1007 return hostversions(sess, mod, rx, snac, bs); | |
1008 else if (snac->subtype == 0x001f) | |
1009 return memrequest(sess, mod, rx, snac, bs); | |
5836 | 1010 else if (snac->subtype == 0x0021) |
1011 return aim_parse_extstatus(sess, mod, rx, snac, bs); | |
2703 | 1012 |
1013 return 0; | |
1014 } | |
1015 | |
1016 faim_internal int general_modfirst(aim_session_t *sess, aim_module_t *mod) | |
1017 { | |
1018 | |
1019 mod->family = 0x0001; | |
1020 mod->version = 0x0003; | |
1021 mod->toolid = 0x0110; | |
4071 | 1022 mod->toolversion = 0x0629; |
2703 | 1023 mod->flags = 0; |
1024 strncpy(mod->name, "general", sizeof(mod->name)); | |
1025 mod->snachandler = snachandler; | |
1026 | |
1027 return 0; | |
1028 } |