1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2005-2013 Licq developers <licq-dev@googlegroups.com>
4  *
5  * Licq is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * Licq is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Licq; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "msn.h"
21 #include "msnevent.h"
22 
23 #include <licq/logging/log.h>
24 
25 #include <cstring>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 
31 #include <licq/contactlist/user.h>
32 #include <licq/plugin/pluginmanager.h>
33 #include <licq/pluginsignal.h>
34 #include <licq/socket.h>
35 
36 using namespace LicqMsn;
37 using Licq::UserId;
38 using Licq::gLog;
39 using std::string;
40 
CMSNDataEvent(CMSN * p)41 CMSNDataEvent::CMSNDataEvent(CMSN *p)
42 {
43   m_pMSN = p;
44   mySocketDesc = NULL;
45   m_nEvent = 0;
46   m_eState = STATE_WAITING_ACK;
47   m_nFileDesc = -1;
48   m_strFileName = "";
49   m_nFilePos = 0;
50   m_nBytesTransferred = 0;
51   m_nStartTime = 0;
52   m_nSessionId = 0;
53   m_nBaseId = 0;
54   m_nDataSize[0] = 0;
55   m_nDataSize[1] = 0;
56   m_strCallId = "";
57 }
58 
CMSNDataEvent(unsigned long _nEvent,unsigned long _nSessionId,unsigned long _nBaseId,const Licq::UserId & userId,const Licq::UserId & fromId,const string & _strCallId,CMSN * p)59 CMSNDataEvent::CMSNDataEvent(unsigned long _nEvent, unsigned long _nSessionId,
60     unsigned long _nBaseId, const Licq::UserId& userId, const Licq::UserId& fromId,
61     const string &_strCallId, CMSN *p)
62   : myUserId(userId),
63     myFromId(fromId)
64 {
65   m_pMSN = p;
66   mySocketDesc = NULL;
67   m_nEvent = _nEvent;
68   m_eState = STATE_WAITING_ACK;
69   m_nFileDesc = -1;
70   {
71     Licq::UserReadGuard u(userId);
72     m_strFileName = u->pictureFileName();
73   }
74   m_nFilePos = 0;
75   m_nBytesTransferred = 0;
76   m_nStartTime = 0;
77   m_nSessionId = _nSessionId;
78   m_nBaseId = _nBaseId;
79   m_nDataSize[0] = 0;
80   m_nDataSize[1] = 0;
81   m_strCallId = _strCallId;
82 }
83 
~CMSNDataEvent()84 CMSNDataEvent::~CMSNDataEvent()
85 {
86   if (mySocketDesc != NULL)
87     m_pMSN->closeSocket(mySocketDesc);
88 
89   if (m_nFileDesc)
90     close(m_nFileDesc);
91 }
92 
ProcessPacket(CMSNBuffer * p)93 int CMSNDataEvent::ProcessPacket(CMSNBuffer *p)
94 {
95   unsigned long nSessionId,
96     nIdentifier,
97     nOffset[2],
98     nDataSize[2],
99     nLen,
100     nFlag,
101     nAckId,
102     nAckUniqueId,
103     nAckSize[2];
104 
105   (*p) >> nSessionId >> nIdentifier >> nOffset[0] >> nOffset[1]
106     >> nDataSize[0] >> nDataSize[1] >> nLen >> nFlag >> nAckId
107     >> nAckUniqueId >> nAckSize[0] >> nAckSize[1];
108 
109 //  printf("%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
110 //	 nSessionId, nIdentifier, nOffset[0], nOffset[1],
111 //	 nDataSize[0], nDataSize[1], nLen, nFlag, nAckId,
112 //	 nAckUniqueId, nAckSize[0], nAckSize[1]);
113 
114   switch (m_eState)
115   {
116     case STATE_WAITING_ACK:
117     {
118       if (m_nSessionId == 0)
119       {
120 	if (nFlag == 0x00000002)
121 	{
122 	  gLog.info("Display Picture: Ack received");
123 	}
124 	else if (nFlag == 0)
125 	{
126 	  if (nSessionId)
127 	    m_nSessionId = nSessionId;
128 	  else
129 	  {
130 	    // Get it from the body
131 	    int nToRead = strstr(p->getDataPosRead(), "\r\n")+2-p->getDataPosRead();
132 	    string strStatus = p->unpackRawString(nToRead);
133 	    if (strStatus != "MSNSLP/1.0 200 OK\r\n")
134 	    {
135 	      gLog.error("Display Picture: Encountered an error before the "
136                   "session id was received: %s", strStatus.c_str());
137 	      // close connection
138 	      return -1;
139 	    }
140 
141 	    p->ParseHeaders();
142 	    string strLen = p->GetValue("Content-Length");
143 	    int nConLen = atoi(strLen.c_str());
144 	    if (nConLen)
145 	    {
146 	      p->SkipRN();
147 	      p->ParseHeaders();
148 	      string strSessId = p->GetValue("SessionID");
149 	      m_nSessionId = strtoul(strSessId.c_str(), (char **)NULL, 10);
150 	    }
151 	  }
152 
153 	  gLog.info("Display Picture: Session Id received (%ld)",
154 		    m_nSessionId);
155 	  CMSNPacket* pAck = new CPS_MSNP2PAck(myUserId.accountId(), m_nSessionId,
156 					       m_nBaseId-3, nIdentifier, nAckId,
157 					       nDataSize[1], nDataSize[0]);
158           m_pMSN->Send_SB_Packet(myUserId, pAck, mySocketDesc);
159 	  m_eState = STATE_GOT_SID;
160 	}
161       }
162 
163 
164       break;
165     }
166 
167     case STATE_GOT_SID:
168     {
169       CMSNPacket* pAck = new CPS_MSNP2PAck(myUserId.accountId(), m_nSessionId,
170 					   m_nBaseId-2, nIdentifier, nAckId,
171 					   nDataSize[1], nDataSize[0]);
172       m_pMSN->Send_SB_Packet(myUserId, pAck, mySocketDesc);
173       m_eState = STATE_RECV_DATA;
174 
175       gLog.info("Display Picture: Got data start message (%ld)",
176 		m_nSessionId);
177 
178       m_nFileDesc = open(m_strFileName.c_str(), O_WRONLY | O_CREAT, 00600);
179       if (!m_nFileDesc)
180       {
181 	gLog.error("Unable to create a file in your licq directory, check disk space");
182 	return -1;
183       }
184 
185       break;
186     }
187 
188     case STATE_RECV_DATA:
189     {
190       // Picture data has the 0x20 Flag, so only set data when we get the first picture data packet
191       if (m_nDataSize[0] == 0 && nFlag == 0x00000020)
192       {
193 	m_nDataSize[0] = nDataSize[0];
194 	m_nDataSize[1] = nDataSize[1];
195 	gLog.info("Display Picture: Expecting file of size %ld (Id: %ld)",
196                   m_nDataSize[0], m_nSessionId);
197       }
198 
199       if (nFlag != 0x00000020)
200       {
201         gLog.info("Display Picture: Skipping packet without 0x20 flag");
202         break;
203       }
204 
205       ssize_t nWrote = write(m_nFileDesc, p->getDataPosRead(), nLen);
206       if (nWrote != (ssize_t)nLen)
207       {
208 	gLog.error("Display Picture: Tried to write %ld, but wrote %ld (Id: %ld)",
209 		   nLen, (long)nWrote, m_nSessionId);
210       }
211 
212       m_nBytesTransferred += nLen;
213 
214       gLog.info("Display Picture: Wrote %ld of %ld bytes",
215                 m_nBytesTransferred, m_nDataSize[0]);
216 
217       if (m_nBytesTransferred >= m_nDataSize[0])
218       {
219 	if (m_nBytesTransferred == m_nDataSize[0])
220 	{
221 	  gLog.info("Display Picture: Successfully completed (%s)",
222                     m_strFileName.c_str());
223 	}
224 	else
225 	{
226 	  gLog.error("Display Picture: Too much data received, ending transfer");
227 	}
228 	close(m_nFileDesc);
229 	m_nFileDesc = -1;
230 	m_eState = STATE_FINISHED;
231 
232         {
233           Licq::UserWriteGuard u(myUserId);
234           if (u.isLocked())
235           {
236             u->SetPicturePresent(true);
237             Licq::gPluginManager.pushPluginSignal(new Licq::PluginSignal(
238                 Licq::PluginSignal::SignalUser,
239                 Licq::PluginSignal::UserPicture, u->id()));
240           }
241         }
242 
243 	// Ack that we got the data
244 	CMSNPacket* pAck = new CPS_MSNP2PAck(myUserId.accountId(), m_nSessionId,
245 					     m_nBaseId-1, nIdentifier, nAckId,
246 					     nDataSize[1], nDataSize[0]);
247         m_pMSN->Send_SB_Packet(myUserId, pAck, mySocketDesc);
248 
249         // Send a bye command
250         CMSNPacket* pBye = new CPS_MSNP2PBye(myUserId.accountId(), myFromId.accountId(),
251             m_strCallId, m_nBaseId, nAckId, nDataSize[1], nDataSize[0]);
252         m_pMSN->Send_SB_Packet(myUserId, pBye, mySocketDesc);
253 	return 0;
254       }
255 
256       break;
257     }
258 
259     case STATE_FINISHED:
260     {
261       // Don't have to send anything back, just return and close the socket.
262       gLog.info("Display Picture: closing connection with %s", myUserId.accountId().c_str());
263       return 10;
264       break;
265     }
266 
267     case STATE_CLOSED:
268     {
269       break;
270     }
271   }
272 
273   return 0;
274 }
275