| 17 License along with this library; if not, write to the Free |
17 License along with this library; if not, write to the Free |
| 18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 */ |
19 */ |
| 20 |
20 |
| 21 #include <glib.h> |
21 #include <glib.h> |
| |
22 #include <gmp.h> |
| 22 #include <string.h> |
23 #include <string.h> |
| 23 |
24 |
| 24 #include "mw_channel.h" |
25 #include "mw_channel.h" |
| 25 #include "mw_cipher.h" |
26 #include "mw_cipher.h" |
| 26 #include "mw_debug.h" |
27 #include "mw_debug.h" |
| 46 |
47 |
| 47 /** provides I/O and callback functions */ |
48 /** provides I/O and callback functions */ |
| 48 struct mwSessionHandler *handler; |
49 struct mwSessionHandler *handler; |
| 49 |
50 |
| 50 enum mwSessionState state; /**< session state */ |
51 enum mwSessionState state; /**< session state */ |
| 51 guint32 state_info; /**< additional state info */ |
52 gpointer state_info; /**< additional state info */ |
| 52 |
53 |
| 53 /* input buffering for an incoming message */ |
54 /* input buffering for an incoming message */ |
| 54 char *buf; /**< buffer for incoming message data */ |
55 char *buf; /**< buffer for incoming message data */ |
| 55 gsize buf_len; /**< length of buf */ |
56 gsize buf_len; /**< length of buf */ |
| 56 gsize buf_used; /**< offset to last-used byte of buf */ |
57 gsize buf_used; /**< offset to last-used byte of buf */ |
| 216 |
217 |
| 217 s->handler->io_close(s); |
218 s->handler->io_close(s); |
| 218 } |
219 } |
| 219 |
220 |
| 220 |
221 |
| 221 /** set the state of the session, and trigger the session handler's |
|
| 222 on_stateChange function. Has no effect if the session is already |
|
| 223 in the specified state (ignores additional state info) |
|
| 224 |
|
| 225 @param s the session |
|
| 226 @param state the state to set |
|
| 227 @param info additional state info |
|
| 228 */ |
|
| 229 static void state(struct mwSession *s, enum mwSessionState state, |
222 static void state(struct mwSession *s, enum mwSessionState state, |
| 230 guint32 info) { |
223 gpointer info) { |
| 231 |
224 |
| 232 struct mwSessionHandler *sh; |
225 struct mwSessionHandler *sh; |
| 233 |
226 |
| 234 g_return_if_fail(s != NULL); |
227 g_return_if_fail(s != NULL); |
| 235 g_return_if_fail(s->handler != NULL); |
228 g_return_if_fail(s->handler != NULL); |
| 237 if(mwSession_isState(s, state)) return; |
230 if(mwSession_isState(s, state)) return; |
| 238 |
231 |
| 239 s->state = state; |
232 s->state = state; |
| 240 s->state_info = info; |
233 s->state_info = info; |
| 241 |
234 |
| 242 if(info) { |
235 switch(state) { |
| 243 g_message("session state: %s (0x%08x)", state_str(state), info); |
236 case mwSession_STOPPING: |
| 244 } else { |
237 case mwSession_STOPPED: |
| |
238 g_message("session state: %s (0x%08x)", state_str(state), |
| |
239 GPOINTER_TO_UINT(info)); |
| |
240 break; |
| |
241 |
| |
242 case mwSession_LOGIN_REDIR: |
| |
243 g_message("session state: %s (%s)", state_str(state), |
| |
244 (char *)info); |
| |
245 break; |
| |
246 |
| |
247 default: |
| 245 g_message("session state: %s", state_str(state)); |
248 g_message("session state: %s", state_str(state)); |
| 246 } |
249 } |
| 247 |
250 |
| 248 sh = s->handler; |
251 sh = s->handler; |
| 249 if(sh->on_stateChange) |
252 if(sh && sh->on_stateChange) |
| 250 sh->on_stateChange(s, state, info); |
253 sh->on_stateChange(s, state, info); |
| 251 } |
254 } |
| 252 |
255 |
| 253 |
256 |
| 254 void mwSession_start(struct mwSession *s) { |
257 void mwSession_start(struct mwSession *s) { |
| 256 int ret; |
259 int ret; |
| 257 |
260 |
| 258 g_return_if_fail(s != NULL); |
261 g_return_if_fail(s != NULL); |
| 259 g_return_if_fail(mwSession_isStopped(s)); |
262 g_return_if_fail(mwSession_isStopped(s)); |
| 260 |
263 |
| |
264 if(mwSession_isStarted(s) || mwSession_isStarting(s)) { |
| |
265 g_debug("attempted to start session that is already started/starting"); |
| |
266 return; |
| |
267 } |
| |
268 |
| 261 state(s, mwSession_STARTING, 0); |
269 state(s, mwSession_STARTING, 0); |
| 262 |
270 |
| 263 msg = (struct mwMsgHandshake *) mwMessage_new(mwMessage_HANDSHAKE); |
271 msg = (struct mwMsgHandshake *) mwMessage_new(mwMessage_HANDSHAKE); |
| 264 msg->major = GUINT(property_get(s, mwSession_CLIENT_VER_MAJOR)); |
272 msg->major = GUINT(property_get(s, mwSession_CLIENT_VER_MAJOR)); |
| 265 msg->minor = GUINT(property_get(s, mwSession_CLIENT_VER_MINOR)); |
273 msg->minor = GUINT(property_get(s, mwSession_CLIENT_VER_MINOR)); |
| 266 msg->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); |
274 msg->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); |
| 267 |
275 |
| |
276 msg->loclcalc_addr = GUINT(property_get(s, mwSession_CLIENT_IP)); |
| |
277 |
| |
278 if(msg->major >= 0x001e && msg->minor >= 0x001d) { |
| |
279 msg->unknown_a = 0x0100; |
| |
280 msg->local_host = (char *) property_get(s, mwSession_CLIENT_HOST); |
| |
281 } |
| |
282 |
| 268 ret = mwSession_send(s, MW_MESSAGE(msg)); |
283 ret = mwSession_send(s, MW_MESSAGE(msg)); |
| 269 mwMessage_free(MW_MESSAGE(msg)); |
284 mwMessage_free(MW_MESSAGE(msg)); |
| 270 |
285 |
| 271 if(ret) { |
286 if(ret) { |
| 272 mwSession_stop(s, CONNECTION_BROKEN); |
287 mwSession_stop(s, CONNECTION_BROKEN); |
| 279 void mwSession_stop(struct mwSession *s, guint32 reason) { |
294 void mwSession_stop(struct mwSession *s, guint32 reason) { |
| 280 GList *list, *l = NULL; |
295 GList *list, *l = NULL; |
| 281 struct mwMsgChannelDestroy *msg; |
296 struct mwMsgChannelDestroy *msg; |
| 282 |
297 |
| 283 g_return_if_fail(s != NULL); |
298 g_return_if_fail(s != NULL); |
| 284 g_return_if_fail(! mwSession_isStopping(s)); |
299 |
| 285 g_return_if_fail(! mwSession_isStopped(s)); |
300 if(mwSession_isStopped(s) || mwSession_isStopping(s)) { |
| 286 |
301 g_debug("attempted to stop session that is already stopped/stopping"); |
| 287 state(s, mwSession_STOPPING, reason); |
302 return; |
| |
303 } |
| |
304 |
| |
305 state(s, mwSession_STOPPING, GUINT_TO_POINTER(reason)); |
| 288 |
306 |
| 289 for(list = l = mwSession_getServices(s); l; l = l->next) |
307 for(list = l = mwSession_getServices(s); l; l = l->next) |
| 290 mwService_stop(MW_SERVICE(l->data)); |
308 mwService_stop(MW_SERVICE(l->data)); |
| 291 g_list_free(list); |
309 g_list_free(list); |
| 292 |
310 |
| 303 session_buf_free(s); |
321 session_buf_free(s); |
| 304 |
322 |
| 305 /* close the connection */ |
323 /* close the connection */ |
| 306 io_close(s); |
324 io_close(s); |
| 307 |
325 |
| 308 state(s, mwSession_STOPPED, reason); |
326 state(s, mwSession_STOPPED, GUINT_TO_POINTER(reason)); |
| 309 } |
327 } |
| 310 |
328 |
| 311 |
329 |
| 312 /** compose authentication information into an opaque based on the |
330 /** compose authentication information into an opaque based on the |
| 313 password */ |
331 password, encrypted via RC2/40 */ |
| 314 static void compose_auth(struct mwOpaque *auth, const char *pass) { |
332 static void compose_auth_rc2_40(struct mwOpaque *auth, const char *pass) { |
| 315 char iv[8], key[5]; |
333 char iv[8], key[5]; |
| 316 struct mwOpaque a, b, z; |
334 struct mwOpaque a, b, z; |
| 317 struct mwPutBuffer *p; |
335 struct mwPutBuffer *p; |
| 318 |
336 |
| 319 /* get an IV and a random five-byte key */ |
337 /* get an IV and a random five-byte key */ |
| 320 mwIV_init((char *) iv); |
338 mwIV_init((char *) iv); |
| 321 rand_key((char *) key, 5); |
339 mwKeyRandom((char *) key, 5); |
| 322 |
340 |
| 323 /* the opaque with the key */ |
341 /* the opaque with the key */ |
| 324 a.len = 5; |
342 a.len = 5; |
| 325 a.data = key; |
343 a.data = key; |
| 326 |
344 |
| 341 mwOpaque_put(p, &b); |
359 mwOpaque_put(p, &b); |
| 342 mwPutBuffer_finalize(auth, p); |
360 mwPutBuffer_finalize(auth, p); |
| 343 |
361 |
| 344 /* this is the only one to clear, as the other uses a static buffer */ |
362 /* this is the only one to clear, as the other uses a static buffer */ |
| 345 mwOpaque_clear(&b); |
363 mwOpaque_clear(&b); |
| |
364 } |
| |
365 |
| |
366 |
| |
367 static void compose_auth_rc2_128(struct mwOpaque *auth, const char *pass, |
| |
368 guint32 magic, struct mwOpaque *rkey) { |
| |
369 |
| |
370 char iv[8]; |
| |
371 struct mwOpaque a, b, c; |
| |
372 struct mwPutBuffer *p; |
| |
373 |
| |
374 mpz_t private, public; |
| |
375 mpz_t remote; |
| |
376 mpz_t shared; |
| |
377 |
| |
378 mpz_init(private); |
| |
379 mpz_init(public); |
| |
380 mpz_init(remote); |
| |
381 mpz_init(shared); |
| |
382 |
| |
383 mwIV_init(iv); |
| |
384 |
| |
385 mwDHRandKeypair(private, public); |
| |
386 mwDHImportKey(remote, rkey); |
| |
387 mwDHCalculateShared(shared, remote, private); |
| |
388 |
| |
389 /* put the password in opaque a */ |
| |
390 p = mwPutBuffer_new(); |
| |
391 guint32_put(p, magic); |
| |
392 mwString_put(p, pass); |
| |
393 mwPutBuffer_finalize(&a, p); |
| |
394 |
| |
395 /* put the shared key in opaque b */ |
| |
396 mwDHExportKey(shared, &b); |
| |
397 |
| |
398 /* encrypt the password (a) using the shared key (b), put the result |
| |
399 in opaque c */ |
| |
400 mwEncrypt(b.data+(b.len-16), 16, iv, &a, &c); |
| |
401 |
| |
402 /* don't need the shared key anymore, re-use opaque (b) as the |
| |
403 export of the public key */ |
| |
404 mwOpaque_clear(&b); |
| |
405 mwDHExportKey(public, &b); |
| |
406 |
| |
407 p = mwPutBuffer_new(); |
| |
408 guint16_put(p, 0x0001); /* XXX: unknown */ |
| |
409 mwOpaque_put(p, &b); |
| |
410 mwOpaque_put(p, &c); |
| |
411 mwPutBuffer_finalize(auth, p); |
| |
412 |
| |
413 mwOpaque_clear(&a); |
| |
414 mwOpaque_clear(&b); |
| |
415 mwOpaque_clear(&c); |
| |
416 |
| |
417 mpz_clear(private); |
| |
418 mpz_clear(public); |
| |
419 mpz_clear(remote); |
| |
420 mpz_clear(shared); |
| 346 } |
421 } |
| 347 |
422 |
| 348 |
423 |
| 349 /** handle the receipt of a handshake_ack message by sending the login |
424 /** handle the receipt of a handshake_ack message by sending the login |
| 350 message */ |
425 message */ |
| 378 log = (struct mwMsgLogin *) mwMessage_new(mwMessage_LOGIN); |
453 log = (struct mwMsgLogin *) mwMessage_new(mwMessage_LOGIN); |
| 379 log->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); |
454 log->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); |
| 380 log->name = g_strdup(property_get(s, mwSession_AUTH_USER_ID)); |
455 log->name = g_strdup(property_get(s, mwSession_AUTH_USER_ID)); |
| 381 |
456 |
| 382 /** @todo default to password for now. later use token optionally */ |
457 /** @todo default to password for now. later use token optionally */ |
| 383 log->auth_type = mwAuthType_ENCRYPT; |
458 { |
| 384 compose_auth(&log->auth_data, property_get(s, mwSession_AUTH_PASSWORD)); |
459 const char *pw; |
| |
460 pw = (const char *) property_get(s, mwSession_AUTH_PASSWORD); |
| |
461 |
| |
462 if(msg->data.len >= 64) { |
| |
463 /* good login encryption */ |
| |
464 log->auth_type = mwAuthType_RC2_128; |
| |
465 compose_auth_rc2_128(&log->auth_data, pw, msg->magic, &msg->data); |
| |
466 |
| |
467 } else { |
| |
468 /* BAD login encryption */ |
| |
469 log->auth_type = mwAuthType_RC2_40; |
| |
470 compose_auth_rc2_40(&log->auth_data, pw); |
| |
471 } |
| |
472 } |
| 385 |
473 |
| 386 /* send the login message */ |
474 /* send the login message */ |
| 387 ret = mwSession_send(s, MW_MESSAGE(log)); |
475 ret = mwSession_send(s, MW_MESSAGE(log)); |
| 388 mwMessage_free(MW_MESSAGE(log)); |
476 mwMessage_free(MW_MESSAGE(log)); |
| 389 |
477 |
| 522 if(sh && sh->on_admin) |
610 if(sh && sh->on_admin) |
| 523 sh->on_admin(s, msg->text); |
611 sh->on_admin(s, msg->text); |
| 524 } |
612 } |
| 525 |
613 |
| 526 |
614 |
| |
615 static void ANNOUNCE_recv(struct mwSession *s, struct mwMsgAnnounce *msg) { |
| |
616 struct mwSessionHandler *sh = s->handler; |
| |
617 |
| |
618 if(sh && sh->on_announce) |
| |
619 sh->on_announce(s, &msg->sender, msg->may_reply, msg->text); |
| |
620 } |
| |
621 |
| |
622 |
| 527 static void LOGIN_REDIRECT_recv(struct mwSession *s, |
623 static void LOGIN_REDIRECT_recv(struct mwSession *s, |
| 528 struct mwMsgLoginRedirect *msg) { |
624 struct mwMsgLoginRedirect *msg) { |
| 529 struct mwSessionHandler *sh = s->handler; |
625 |
| 530 |
626 state(s, mwSession_LOGIN_REDIR, msg->host); |
| 531 state(s, mwSession_LOGIN_REDIR, 0); |
|
| 532 |
|
| 533 if(sh && sh->on_loginRedirect) |
|
| 534 sh->on_loginRedirect(s, msg->host); |
|
| 535 } |
627 } |
| 536 |
628 |
| 537 |
629 |
| 538 #define CASE(var, type) \ |
630 #define CASE(var, type) \ |
| 539 case mwMessage_ ## var: \ |
631 case mwMessage_ ## var: \ |
| 542 |
634 |
| 543 |
635 |
| 544 static void session_process(struct mwSession *s, |
636 static void session_process(struct mwSession *s, |
| 545 const char *buf, gsize len) { |
637 const char *buf, gsize len) { |
| 546 |
638 |
| 547 struct mwOpaque o = { len, (char *) buf }; |
639 struct mwOpaque o = { .len = len, .data = (char *) buf }; |
| 548 struct mwGetBuffer *b; |
640 struct mwGetBuffer *b; |
| 549 struct mwMessage *msg; |
641 struct mwMessage *msg; |
| 550 |
642 |
| 551 g_return_if_fail(s != NULL); |
643 g_return_if_fail(s != NULL); |
| 552 g_return_if_fail(buf != NULL); |
644 g_return_if_fail(buf != NULL); |
| 557 /* wrap up buf */ |
649 /* wrap up buf */ |
| 558 b = mwGetBuffer_wrap(&o); |
650 b = mwGetBuffer_wrap(&o); |
| 559 |
651 |
| 560 /* attempt to parse the message. */ |
652 /* attempt to parse the message. */ |
| 561 msg = mwMessage_get(b); |
653 msg = mwMessage_get(b); |
| |
654 |
| |
655 if(mwGetBuffer_error(b)) { |
| |
656 mw_mailme_opaque(&o, "parsing of message failed"); |
| |
657 } |
| |
658 |
| |
659 mwGetBuffer_free(b); |
| |
660 |
| 562 g_return_if_fail(msg != NULL); |
661 g_return_if_fail(msg != NULL); |
| 563 |
662 |
| 564 /* handle each of the appropriate incoming types of mwMessage */ |
663 /* handle each of the appropriate incoming types of mwMessage */ |
| 565 switch(msg->type) { |
664 switch(msg->type) { |
| 566 CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); |
665 CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); |
| 572 CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); |
671 CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); |
| 573 CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); |
672 CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); |
| 574 CASE(SET_USER_STATUS, mwMsgSetUserStatus); |
673 CASE(SET_USER_STATUS, mwMsgSetUserStatus); |
| 575 CASE(SENSE_SERVICE, mwMsgSenseService); |
674 CASE(SENSE_SERVICE, mwMsgSenseService); |
| 576 CASE(ADMIN, mwMsgAdmin); |
675 CASE(ADMIN, mwMsgAdmin); |
| |
676 CASE(ANNOUNCE, mwMsgAnnounce); |
| 577 |
677 |
| 578 default: |
678 default: |
| 579 g_warning("unknown message type 0x%04x, no handler", msg->type); |
679 g_warning("unknown message type 0x%04x, no handler", msg->type); |
| 580 } |
680 } |
| 581 |
681 |
| 582 if(mwGetBuffer_error(b)) { |
|
| 583 struct mwOpaque o = { .data = (char *) buf, .len = len }; |
|
| 584 mw_debug_mailme(&o, "parsing of message type 0x%04x failed", msg->type); |
|
| 585 } |
|
| 586 |
|
| 587 mwGetBuffer_free(b); |
|
| 588 mwMessage_free(msg); |
682 mwMessage_free(msg); |
| 589 } |
683 } |
| 590 |
684 |
| 591 |
685 |
| 592 #undef CASE |
686 #undef CASE |
| 593 |
687 |
| 594 |
688 |
| 595 #define ADVANCE(b, n, count) (b += count, n -= count) |
689 #define ADVANCE(b, n, count) { b += count; n -= count; } |
| 596 |
690 |
| 597 |
691 |
| 598 /* handle input to complete an existing buffer */ |
692 /* handle input to complete an existing buffer */ |
| 599 static gsize session_recv_cont(struct mwSession *s, const char *b, gsize n) { |
693 static gsize session_recv_cont(struct mwSession *s, const char *b, gsize n) { |
| 600 |
694 |
| 737 obscenely long message (even if it never actually sends it) */ |
831 obscenely long message (even if it never actually sends it) */ |
| 738 |
832 |
| 739 /* g_message(" session_recv: session = %p, b = %p, n = %u", |
833 /* g_message(" session_recv: session = %p, b = %p, n = %u", |
| 740 s, b, n); */ |
834 s, b, n); */ |
| 741 |
835 |
| 742 if(n && (s->buf_len == 0) && (*b & 0x80)) { |
836 if(s->buf_len == 0) { |
| 743 /* keep-alive and series bytes are ignored */ |
837 while(n && (*b & 0x80)) { |
| 744 ADVANCE(b, n, 1); |
838 /* keep-alive and series bytes are ignored */ |
| |
839 ADVANCE(b, n, 1); |
| |
840 } |
| 745 } |
841 } |
| 746 |
842 |
| 747 if(n == 0) { |
843 if(n == 0) { |
| 748 return 0; |
844 return 0; |
| 749 |
845 |
| 762 void mwSession_recv(struct mwSession *s, const char *buf, gsize n) { |
858 void mwSession_recv(struct mwSession *s, const char *buf, gsize n) { |
| 763 char *b = (char *) buf; |
859 char *b = (char *) buf; |
| 764 gsize remain = 0; |
860 gsize remain = 0; |
| 765 |
861 |
| 766 g_return_if_fail(s != NULL); |
862 g_return_if_fail(s != NULL); |
| 767 |
|
| 768 /* g_message(" mwSession_recv: session = %p, b = %p, n = %u", |
|
| 769 s, b, n); */ |
|
| 770 |
863 |
| 771 while(n > 0) { |
864 while(n > 0) { |
| 772 remain = session_recv(s, b, n); |
865 remain = session_recv(s, b, n); |
| 773 b += (n - remain); |
866 b += (n - remain); |
| 774 n = remain; |
867 n = remain; |
| 777 |
870 |
| 778 |
871 |
| 779 int mwSession_send(struct mwSession *s, struct mwMessage *msg) { |
872 int mwSession_send(struct mwSession *s, struct mwMessage *msg) { |
| 780 struct mwPutBuffer *b; |
873 struct mwPutBuffer *b; |
| 781 struct mwOpaque o; |
874 struct mwOpaque o; |
| 782 gsize len; |
|
| 783 int ret = 0; |
875 int ret = 0; |
| 784 |
876 |
| 785 g_return_val_if_fail(s != NULL, -1); |
877 g_return_val_if_fail(s != NULL, -1); |
| 786 |
878 |
| 787 /* writing nothing is easy */ |
879 /* writing nothing is easy */ |
| 797 mwOpaque_put(b, &o); |
889 mwOpaque_put(b, &o); |
| 798 mwOpaque_clear(&o); |
890 mwOpaque_clear(&o); |
| 799 mwPutBuffer_finalize(&o, b); |
891 mwPutBuffer_finalize(&o, b); |
| 800 |
892 |
| 801 /* then we use that opaque's data and length to write to the socket */ |
893 /* then we use that opaque's data and length to write to the socket */ |
| 802 len = o.len; |
|
| 803 ret = io_write(s, o.data, o.len); |
894 ret = io_write(s, o.data, o.len); |
| 804 mwOpaque_clear(&o); |
895 mwOpaque_clear(&o); |
| 805 |
896 |
| 806 /* ensure we could actually write the message */ |
897 /* ensure we could actually write the message */ |
| 807 if(! ret) { |
898 if(! ret) { |
| 843 |
934 |
| 844 return ret; |
935 return ret; |
| 845 } |
936 } |
| 846 |
937 |
| 847 |
938 |
| |
939 int mwSession_sendAnnounce(struct mwSession *s, gboolean may_reply, |
| |
940 const char *text, const GList *recipients) { |
| |
941 |
| |
942 struct mwMsgAnnounce *msg; |
| |
943 int ret; |
| |
944 |
| |
945 g_return_val_if_fail(s != NULL, -1); |
| |
946 g_return_val_if_fail(mwSession_isStarted(s), -1); |
| |
947 |
| |
948 msg = (struct mwMsgAnnounce *) mwMessage_new(mwMessage_ANNOUNCE); |
| |
949 |
| |
950 msg->recipients = (GList *) recipients; |
| |
951 msg->may_reply = may_reply; |
| |
952 msg->text = g_strdup(text); |
| |
953 |
| |
954 ret = mwSession_send(s, MW_MESSAGE(msg)); |
| |
955 |
| |
956 msg->recipients = NULL; /* don't kill our recipients param */ |
| |
957 mwMessage_free(MW_MESSAGE(msg)); |
| |
958 |
| |
959 return ret; |
| |
960 } |
| |
961 |
| |
962 |
| 848 struct mwSessionHandler *mwSession_getHandler(struct mwSession *s) { |
963 struct mwSessionHandler *mwSession_getHandler(struct mwSession *s) { |
| 849 g_return_val_if_fail(s != NULL, NULL); |
964 g_return_val_if_fail(s != NULL, NULL); |
| 850 return s->handler; |
965 return s->handler; |
| 851 } |
966 } |
| 852 |
967 |