#include "StdAfx.h" #include "sametime.h" /// not in CSametimeProto (used at NETLIBOPENCONNECTION_tag.waitcallback) bool continue_connect; #define MS_SAMETIME_MENUANNOUNCESESSION "/SessionAnnounce" // utf8 encoded struct { char* szOnline; char* szAway; char* szDND; } AwayMessages; void __cdecl SessionClear(mwSession* session) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionClear()")); } int __cdecl SessionWrite(mwSession* session, const unsigned char* buf, gsize len) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionWrite() server_connection=[%d], len=[%d]"), proto->server_connection, len); if (!proto->server_connection) return 1; if (Netlib_Send(proto->server_connection, (const char*)buf, len, 0) == SOCKET_ERROR) return 1; return 0; } void __cdecl SessionClose(mwSession* session) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionClose() server_connection=[%d]"), proto->server_connection); Netlib_CloseHandle(proto->server_connection); proto->server_connection = 0; } void CSametimeProto::SessionStarted() { UserListCreate(); if (options.get_server_contacts) UserListAddStored(); } void CSametimeProto::SessionStopping() { UserListDestroy(); } void CSametimeProto::InitMeanwhileServices() { debugLog(_T("InitMeanwhileServices() start")); if (options.encrypt_session) { mwSession_addCipher(session, mwCipher_new_RC2_128(session)); mwSession_addCipher(session, mwCipher_new_RC2_40(session)); } InitUserList(); InitMessaging(); InitFiles(); InitConference(); mwSession_setProperty(session, "PROTO_STRUCT_PTR", this, NULL); mwSession_setProperty(session, mwSession_AUTH_USER_ID, options.id, NULL); mwSession_setProperty(session, mwSession_AUTH_PASSWORD, options.pword, NULL); mwSession_setProperty(session, mwSession_CLIENT_TYPE_ID, (void*)options.client_id, NULL); mwSession_setProperty(session, mwSession_CLIENT_VER_MAJOR, (void*)options.client_versionMajor, NULL); mwSession_setProperty(session, mwSession_CLIENT_VER_MINOR, (void*)options.client_versionMinor, NULL); } void CSametimeProto::DeinitMeanwhileServices() { debugLog(_T("DeinitMeanwhileServices() start")); DeinitConference(); DeinitFiles(); DeinitMessaging(); DeinitUserList(); mwCipher_free(mwSession_getCipher(session, mwCipher_RC2_40)); mwCipher_free(mwSession_getCipher(session, mwCipher_RC2_128)); } void __cdecl SessionStateChange(mwSession* session, mwSessionState state, gpointer info) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionStateChange() state=[%d]"), state); switch (state) { case mwSession_STARTING: break; case mwSession_HANDSHAKE: break; case mwSession_HANDSHAKE_ACK: break; case mwSession_STARTED: proto->SessionStarted(); break; case mwSession_STOPPING: if ((int)info) {// & ERR_FAILURE) { proto->showPopup((int)info); } proto->SessionStopping(); break; case mwSession_STOPPED: break; case mwSession_LOGIN_REDIR: proto->debugLog(_T("SessionStateChange() mwSession_LOGIN_REDIR info=[%s]"), _A2T((char*)info)); //options.server_name = str((char*)info); strcpy(proto->options.server_name, (char*)info); proto->LogOut(); proto->LogIn(proto->login_status, proto->m_hNetlibUser); break; case mwSession_LOGIN_CONT: break; case mwSession_LOGIN: break; case mwSession_LOGIN_ACK: break; case mwSession_UNKNOWN: break; } } void __cdecl SessionAdmin(struct mwSession* session, const char* text) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionAdmin()")); TCHAR* tt = mir_utf8decodeT(text); MessageBox(0, tt, TranslateT("Sametime administrator message"), MB_OK); mir_free(tt); } void __cdecl SessionAnnounce(struct mwSession* session, struct mwLoginInfo* from, gboolean may_reply, const char* text) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionAnnounce()")); TCHAR* stzFrom; TCHAR* stzText; TCHAR stzFromBuff[256]; stzFrom = mir_utf8decodeT(from->user_name); stzText = mir_utf8decodeT(text); mir_sntprintf(stzFromBuff, 256, TranslateT("Session announcement - from '%s'"), stzFrom); MessageBox(0, TranslateTS(stzText), stzFromBuff, MB_OK); mir_free(stzText); mir_free(stzFrom); } void __cdecl SessionSetPrivacyInfo(struct mwSession* session) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); proto->debugLog(_T("SessionSetPrivacyInfo()")); } void __cdecl SessionSetUserStatus(struct mwSession* session) { CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR"); int new_status; struct mwUserStatus us; mwUserStatus_clone(&us, mwSession_getUserStatus(session)); proto->debugLog(_T("SessionSetUserStatus() us.status=[%d]"), us.status); switch (us.status) { case mwStatus_ACTIVE: new_status = ID_STATUS_ONLINE; break; case mwStatus_AWAY: new_status = ID_STATUS_AWAY; if (proto->idle_status) { // ignore setting to away by idle module, after we've set ourselves idle // most standard clients represent idle and away the same way anyway, // but this allows miranda users to make use of the idle timestamp // but show our status in clist as away proto->BroadcastNewStatus(new_status); mwUserStatus_clear(&us); return; } break; case mwStatus_BUSY: new_status = ID_STATUS_DND; break; case mwStatus_IDLE: new_status = ID_STATUS_AWAY; if (!proto->first_online && !proto->options.idle_as_away) { // show our status in clist as away if idle when going online or treating idle as away mwUserStatus_clear(&us); return; } break; case 8: // new 'in a meeting' status, not handled by meanwhile lib new_status = ID_STATUS_OCCUPIED; break; default: TCHAR buff[512]; mir_sntprintf(buff, 512, TranslateT("Unknown user status: %d"), us.status); proto->showPopup(buff, SAMETIME_POPUP_ERROR); proto->debugLog(buff); mwUserStatus_clear(&us); // just go online...to prevent us getting stuck 'connecting' new_status = ID_STATUS_ONLINE; break; } proto->m_iDesiredStatus = new_status; if (proto->first_online) { proto->first_online = false; //proto->showPopup(TranslateT("Setting login status"), SAMETIME_POPUP_INFO); proto->debugLog(_T("Setting login status")); proto->SetSessionStatus(proto->login_status); } else proto->BroadcastNewStatus(new_status); mwUserStatus_clear(&us); } void CSametimeProto::UpdateSelfStatus() { EnterCriticalSection(&session_cs); if (session) SessionSetUserStatus(session); LeaveCriticalSection(&session_cs); } int CSametimeProto::SetSessionStatus(int status) { struct mwUserStatus us; debugLog(_T("SetSessionStatus() start status=[%d]"), status); if (idle_timerid) KillTimer(0, idle_timerid); us.time = (DWORD)time(0); //us.time = 0; switch (status) { case ID_STATUS_FREECHAT: case ID_STATUS_ONLINE: us.desc = AwayMessages.szOnline; us.status = mwStatus_ACTIVE; break; case ID_STATUS_NA: case ID_STATUS_INVISIBLE: case ID_STATUS_ONTHEPHONE: case ID_STATUS_OUTTOLUNCH: case ID_STATUS_AWAY: us.desc = AwayMessages.szAway; us.status = mwStatus_AWAY; break; case ID_STATUS_OCCUPIED: case ID_STATUS_DND: us.desc = AwayMessages.szDND; us.status = mwStatus_BUSY; break; default: // act as online for unsupported status us.desc = AwayMessages.szOnline; us.status = mwStatus_ACTIVE; break; } debugLog(_T("SetSessionStatus() mwSession_setUserStatus us.status=[%d], us.desc:len=[%d]"), us.status, us.desc == NULL ? -1 : strlen(us.desc)); mwSession_setUserStatus(session, &us); return 0; } VOID CALLBACK IdleTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { CSametimeProto* proto = (CSametimeProto*)idEvent; KillTimer(0, proto->idle_timerid); proto->idle_timerid = 0; if (proto->idle_status) { struct mwUserStatus us; us.time = (DWORD)time(0); us.status = mwStatus_IDLE; us.desc = 0; mwSession_setUserStatus(proto->session, &us); } else proto->SetSessionStatus(proto->m_iStatus); } int CSametimeProto::SetIdle(bool idle) { // set a timer, to wait for any autoaway module which might set our status debugLog(_T("CSametimeProto::SetIdle() idle=[%d], idle_status=[%d], idle_timerid=[%d]"), idle, idle_status, idle_timerid); if (idle && !idle_status) { idle_status = true; if (!idle_timerid) idle_timerid = SetTimer(0, (UINT_PTR)this, 200, IdleTimerProc); } else if (idle_status) { idle_status = false; if (!idle_timerid) idle_timerid = SetTimer(0, (UINT_PTR)this, 200, IdleTimerProc); } return 0; } void CSametimeProto::SetSessionAwayMessage(int status, const PROTOCHAR* msgT) { debugLog(_T("SetSessionAwayMessage() status=[%d], msgT:len=[%d]"), status, msgT == NULL ? -1 : _tcslen(msgT)); ptrA msg(mir_utf8encodeT(msgT)); if (status == ID_STATUS_ONLINE) replaceStr(AwayMessages.szOnline, msg); else if (status == ID_STATUS_AWAY) replaceStr(AwayMessages.szAway, msg); else if (status == ID_STATUS_DND) replaceStr(AwayMessages.szDND, msg); else return; // unsupported status if (session) SetSessionStatus(status); // update current away message } static VOID CALLBACK NullAPC(DWORD_PTR) { // This function intentionally left blank } void WakeThread(HANDLE hThread) { QueueUserAPC(NullAPC, hThread, 0); } void __cdecl KeepAliveThread(LPVOID param) { CSametimeProto* proto = (CSametimeProto*)param; int i = 120; proto->debugLog(_T("KeepAliveThread() start")); while (1) { if (i <= 0) { i = 120; // send keepalive every 120 * 250 = 30000[ms] if (mwSession_isStarted(proto->session) && proto->session) { mwSession_sendKeepalive(proto->session); } } i--; SleepEx(250, TRUE); EnterCriticalSection(&(proto->session_cs)); if (Miranda_Terminated() || !proto->session) { LeaveCriticalSection(&(proto->session_cs)); proto->debugLog(_T("KeepAliveThread() end")); break; } LeaveCriticalSection(&(proto->session_cs)); } return; } int waitcallback(unsigned int* timeout) { return continue_connect ? 1 : 0; } void __cdecl SessionThread(LPVOID param) { CSametimeProto* proto = (CSametimeProto*)param; HANDLE hNetlibUser = proto->m_hNetlibUser; proto->debugLog(_T("SessionThread() start")); continue_connect = true; //setup NETLIBOPENCONNECTION conn_data = { 0 }; conn_data.cbSize = sizeof(NETLIBOPENCONNECTION); conn_data.flags = NLOCF_V2; conn_data.szHost = proto->options.server_name; conn_data.wPort = proto->options.port; conn_data.timeout = 20; conn_data.waitcallback = waitcallback; proto->BroadcastNewStatus(ID_STATUS_CONNECTING); proto->server_connection = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)hNetlibUser, (LPARAM)&conn_data); if (!proto->server_connection) { proto->BroadcastNewStatus(ID_STATUS_OFFLINE); if (continue_connect) { // real timeout - not user cancelled proto->showPopup(TranslateT("No server connection!"), SAMETIME_POPUP_ERROR); } proto->debugLog(_T("SessionThread() end, no server_connection, continue_connect=[%d]"), continue_connect); return; } mwSessionHandler handler = { 0 }; handler.clear = SessionClear; handler.io_write = SessionWrite; handler.io_close = SessionClose; handler.on_stateChange = SessionStateChange; handler.on_admin = SessionAdmin; handler.on_announce = SessionAnnounce; handler.on_setPrivacyInfo = SessionSetPrivacyInfo; handler.on_setUserStatus = SessionSetUserStatus; EnterCriticalSection(&proto->session_cs); proto->session = mwSession_new(&handler); proto->InitMeanwhileServices(); mwSession_start(proto->session); LeaveCriticalSection(&proto->session_cs); mir_forkthread(KeepAliveThread, (void*)proto); unsigned char* recv_buffer = (unsigned char*)mir_alloc(1024 * 32); int bytes; //while(session && server_connection && mwSession_getState(session) != mwSession_STOPPED) { while (proto->server_connection) {// && session) {// && !mwSession_isStopped(session)) { // break on error bytes = Netlib_Recv(proto->server_connection, (char *)recv_buffer, 1024 * 32, 0); proto->debugLog(_T("SessionThread() Netlib_Recv'ed bytes=[%d]"), bytes); if (bytes == 0) { break; } else if (bytes == SOCKET_ERROR) { // this is normal - e.g. socket closed due to log off, during blocking read above break; } else { EnterCriticalSection(&proto->session_cs); mwSession_recv(proto->session, recv_buffer, bytes); LeaveCriticalSection(&proto->session_cs); } } mir_free(recv_buffer); EnterCriticalSection(&proto->session_cs); proto->DeinitMeanwhileServices(); mwSession* old_session = proto->session; proto->session = 0; // kills keepalive thread, if awake mwSession_free(old_session); LeaveCriticalSection(&proto->session_cs); proto->BroadcastNewStatus(ID_STATUS_OFFLINE); proto->SetAllOffline(); proto->first_online = true; proto->debugLog(_T("SessionThread() end")); return; } WORD CSametimeProto::GetClientVersion() { if (!session) return 0; WORD retval = 0; retval = (int)mwSession_getProperty(session, mwSession_CLIENT_VER_MAJOR) << 8; retval |= (int)mwSession_getProperty(session, mwSession_CLIENT_VER_MINOR); return retval; } WORD CSametimeProto::GetServerVersion() { if (!session) return 0; WORD retval = 0; retval = (int)mwSession_getProperty(session, mwSession_SERVER_VER_MAJOR) << 8; retval |= (int)mwSession_getProperty(session, mwSession_SERVER_VER_MINOR); return retval; } int CSametimeProto::LogIn(int ls, HANDLE hNetlibUser) { debugLog(_T("LogIn() start")); EnterCriticalSection(&session_cs); if (session) { LeaveCriticalSection(&session_cs); debugLog(_T("LogIn() end, currently in session")); return 0; } LeaveCriticalSection(&session_cs); login_status = ls; mir_forkthread(SessionThread, (void*)this); return 0; } int CSametimeProto::LogOut() { debugLog(_T("LogOut() start")); continue_connect = false; EnterCriticalSection(&session_cs); if (session && server_connection && m_iStatus != ID_STATUS_OFFLINE && !mwSession_isStopped(session) && !mwSession_isStopping(session)) { debugLog(_T("LogOut() mwSession_stop")); mwSession_stop(session, 0); } LeaveCriticalSection(&session_cs); return 0; } void CSametimeProto::InitAwayMsg() { AwayMessages.szOnline = 0; AwayMessages.szAway = 0; AwayMessages.szDND = 0; } void CSametimeProto::DeinitAwayMsg() { mir_free(AwayMessages.szOnline); mir_free(AwayMessages.szAway); mir_free(AwayMessages.szDND); } void SendAnnouncement(SendAnnouncementFunc_arg* arg) { CSametimeProto* proto = arg->proto; char* utfs = mir_utf8encodeT(arg->msg); if (proto->session && arg->recipients) mwSession_sendAnnounce(proto->session, false, utfs, arg->recipients); mir_free(utfs); } INT_PTR CSametimeProto::SessionAnnounce(WPARAM wParam, LPARAM lParam) { debugLog(_T("CSametimeProto::SessionAnnounce() start")); SessionAnnounceDialogProc_arg* sadpArg = (SessionAnnounceDialogProc_arg*)mir_calloc(sizeof(SessionAnnounceDialogProc_arg)); sadpArg->proto = this; sadpArg->sendAnnouncementFunc = SendAnnouncement; CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SESSIONANNOUNCE), GetDesktopWindow(), SessionAnnounceDialogProc, (LPARAM)sadpArg); return 0; } void CSametimeProto::InitSessionMenu() { debugLog(_T("CSametimeProto::InitSessionMenu()")); CreateProtoService(MS_SAMETIME_MENUANNOUNCESESSION, &CSametimeProto::SessionAnnounce); char service[128]; CLISTMENUITEM mi = { sizeof(mi) }; mi.flags = CMIF_TCHAR; mi.popupPosition = 500085001; mi.position = 2000060000; mi.ptszName = LPGENT("Send announcement..."); mir_snprintf(service, sizeof(service), "%s%s", m_szModuleName, MS_SAMETIME_MENUANNOUNCESESSION); mi.pszService = service; mi.icolibItem = GetIconHandle(IDI_ICON_ANNOUNCE); mi.pszContactOwner = m_szModuleName; hSessionAnnounceMenuItem = Menu_AddContactMenuItem(&mi); } void CSametimeProto::DeinitSessionMenu() { debugLog(_T("CSametimeProto::DeinitSessionMenu()")); CallService(MO_REMOVEMENUITEM, (WPARAM)hSessionAnnounceMenuItem, 0); } void CSametimeProto::InitCritSection() { debugLog(_T("CSametimeProto::InitCritSection()")); InitializeCriticalSection(&session_cs); } void CSametimeProto::DeinitCritSection() { debugLog(_T("CSametimeProto::DeinitCritSection()")); DeleteCriticalSection(&session_cs); }