blob: a20ec01d60aac5d4fb01309ec3e1205ea54ce6ae [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19#include <rfb/VNCSConnectionST.h>
20#include <rfb/LogWriter.h>
21#include <rfb/secTypes.h>
22#include <rfb/ServerCore.h>
23#include <rfb/ComparingUpdateTracker.h>
24#define XK_MISCELLANY
25#define XK_XKB_KEYS
26#include <rfb/keysymdef.h>
27
28using namespace rfb;
29
30static LogWriter vlog("VNCSConnST");
31
32VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
33 bool reverse)
34 : sock(s), reverseConnection(reverse), server(server_),
35 image_getter(server->useEconomicTranslate),
36 drawRenderedCursor(false), removeRenderedCursor(false),
37 pointerEventTime(0), accessRights(AccessDefault)
38{
39 setStreams(&sock->inStream(), &sock->outStream());
40 peerEndpoint.buf = sock->getPeerEndpoint();
41 VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf);
42
43 setSocketTimeouts();
44 lastEventTime = time(0);
45
46 // Initialise security
47 CharArray sec_types_str;
48 if (reverseConnection)
49 sec_types_str.buf = rfb::Server::rev_sec_types.getData();
50 else
51 sec_types_str.buf = rfb::Server::sec_types.getData();
52 std::list<int> sec_types = parseSecTypes(sec_types_str.buf);
53 std::list<int>::iterator i;
54 for (i=sec_types.begin(); i!=sec_types.end(); i++) {
55 addSecType(*i);
56 }
57
58 server->clients.push_front(this);
59}
60
61
62VNCSConnectionST::~VNCSConnectionST()
63{
64 // If we reach here then VNCServerST is deleting us!
65 VNCServerST::connectionsLog.write(1,"closed: %s (%s)",
66 peerEndpoint.buf, closeReason.buf);
67
68 // Release any keys the client still had pressed
69 std::set<rdr::U32>::iterator i;
70 for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++)
71 server->desktop->keyEvent(*i, false);
72 if (server->pointerClient == this)
73 server->pointerClient = 0;
74
75 // Remove this client from the server
76 server->clients.remove(this);
77}
78
79
80// Methods called from VNCServerST
81
82bool VNCSConnectionST::init()
83{
84 try {
85 initialiseProtocol();
86 } catch (rdr::Exception& e) {
87 close(e.str());
88 return false;
89 }
90 return true;
91}
92
93void VNCSConnectionST::close(const char* reason)
94{
95 // Log the reason for the close
96 if (!closeReason.buf)
97 closeReason.buf = strDup(reason);
98 else
99 vlog.debug("second close: %s (%s)", peerEndpoint.buf, reason);
100
101 // Just shutdown the socket. This will cause processMessages to
102 // eventually fail, causing us and our socket to be deleted.
103 sock->shutdown();
104 setState(RFBSTATE_CLOSING);
105}
106
107
108bool VNCSConnectionST::processMessages()
109{
110 if (state() == RFBSTATE_CLOSING) return false;
111 try {
112 // - Now set appropriate socket timeouts and process data
113 setSocketTimeouts();
114 bool clientsReadyBefore = server->clientsReadyForUpdate();
115
116 while (getInStream()->checkNoWait(1)) {
117 processMsg();
118 }
119
120 if (!clientsReadyBefore && !requested.is_empty())
121 server->desktop->framebufferUpdateRequest();
122
123 return true;
124
125 } catch (rdr::EndOfStream&) {
126 close("Clean disconnection");
127 } catch (rdr::Exception &e) {
128 close(e.str());
129 }
130 return false;
131}
132
133void VNCSConnectionST::writeFramebufferUpdateOrClose()
134{
135 try {
136 writeFramebufferUpdate();
137 } catch(rdr::Exception &e) {
138 close(e.str());
139 }
140}
141
142void VNCSConnectionST::pixelBufferChange()
143{
144 try {
145 if (!authenticated()) return;
146 if (cp.width && cp.height && (server->pb->width() != cp.width ||
147 server->pb->height() != cp.height))
148 {
149 // We need to clip the next update to the new size, but also add any
150 // extra bits if it's bigger. If we wanted to do this exactly, something
151 // like the code below would do it, but at the moment we just update the
152 // entire new size. However, we do need to clip the renderedCursorRect
153 // because that might be added to updates in writeFramebufferUpdate().
154
155 //updates.intersect(server->pb->getRect());
156 //
157 //if (server->pb->width() > cp.width)
158 // updates.add_changed(Rect(cp.width, 0, server->pb->width(),
159 // server->pb->height()));
160 //if (server->pb->height() > cp.height)
161 // updates.add_changed(Rect(0, cp.height, cp.width,
162 // server->pb->height()));
163
164 renderedCursorRect = renderedCursorRect.intersect(server->pb->getRect());
165
166 cp.width = server->pb->width();
167 cp.height = server->pb->height();
168 if (!writer()->writeSetDesktopSize()) {
169 close("Client does not support desktop resize");
170 return;
171 }
172 }
173 // Just update the whole screen at the moment because we're too lazy to
174 // work out what's actually changed.
175 updates.clear();
176 updates.add_changed(server->pb->getRect());
177 vlog.debug("pixel buffer changed - re-initialising image getter");
178 image_getter.init(server->pb, cp.pf(), writer());
179 if (writer()->needFakeUpdate())
180 writeFramebufferUpdate();
181 } catch(rdr::Exception &e) {
182 close(e.str());
183 }
184}
185
186void VNCSConnectionST::setColourMapEntriesOrClose(int firstColour,int nColours)
187{
188 try {
189 setColourMapEntries(firstColour, nColours);
190 } catch(rdr::Exception& e) {
191 close(e.str());
192 }
193}
194
195void VNCSConnectionST::bell()
196{
197 try {
198 if (state() == RFBSTATE_NORMAL) writer()->writeBell();
199 } catch(rdr::Exception& e) {
200 close(e.str());
201 }
202}
203
204void VNCSConnectionST::serverCutText(const char *str, int len)
205{
206 try {
207 if (!(accessRights & AccessCutText)) return;
208 if (!rfb::Server::sendCutText) return;
209 if (state() == RFBSTATE_NORMAL)
210 writer()->writeServerCutText(str, len);
211 } catch(rdr::Exception& e) {
212 close(e.str());
213 }
214}
215
216void VNCSConnectionST::setCursorOrClose()
217{
218 try {
219 setCursor();
220 } catch(rdr::Exception& e) {
221 close(e.str());
222 }
223}
224
225
226int VNCSConnectionST::checkIdleTimeout()
227{
228 int idleTimeout = rfb::Server::idleTimeout;
229 if (idleTimeout == 0) return 0;
230 if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
231 idleTimeout = 15; // minimum of 15 seconds while authenticating
232 time_t now = time(0);
233 if (now < lastEventTime) {
234 // Someone must have set the time backwards. Set lastEventTime so that the
235 // idleTimeout will count from now.
236 vlog.info("Time has gone backwards - resetting idle timeout");
237 lastEventTime = now;
238 }
239 int timeLeft = lastEventTime + idleTimeout - now;
240 if (timeLeft < -60) {
241 // Our callback is over a minute late - someone must have set the time
242 // forwards. Set lastEventTime so that the idleTimeout will count from
243 // now.
244 vlog.info("Time has gone forwards - resetting idle timeout");
245 lastEventTime = now;
246 return idleTimeout;
247 }
248 if (timeLeft <= 0) {
249 close("Idle timeout");
250 return 0;
251 }
252 return timeLeft * 1000;
253}
254
255// renderedCursorChange() is called whenever the server-side rendered cursor
256// changes shape or position. It ensures that the next update will clean up
257// the old rendered cursor and if necessary draw the new rendered cursor.
258
259void VNCSConnectionST::renderedCursorChange()
260{
261 if (state() != RFBSTATE_NORMAL) return;
262 removeRenderedCursor = true;
263 if (needRenderedCursor())
264 drawRenderedCursor = true;
265}
266
267// needRenderedCursor() returns true if this client needs the server-side
268// rendered cursor. This may be because it does not support local cursor or
269// because the current cursor position has not been set by this client.
270// Unfortunately we can't know for sure when the current cursor position has
271// been set by this client. We guess that this is the case when the current
272// cursor position is the same as the last pointer event from this client, or
273// if it is a very short time since this client's last pointer event (up to a
274// second). [ Ideally we should do finer-grained timing here and make the time
275// configurable, but I don't think it's that important. ]
276
277bool VNCSConnectionST::needRenderedCursor()
278{
279 return (state() == RFBSTATE_NORMAL
Peter Åstrand1e9d32f2005-02-16 13:00:38 +0000280 && (!cp.supportsLocalCursor && !cp.supportsLocalXCursor
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000281 || (!server->cursorPos.equals(pointerEventPos) &&
282 (time(0) - pointerEventTime) > 0)));
283}
284
285
286void VNCSConnectionST::approveConnectionOrClose(bool accept,
287 const char* reason)
288{
289 try {
290 approveConnection(accept, reason);
291 } catch (rdr::Exception& e) {
292 close(e.str());
293 }
294}
295
296
297
298// -=- Callbacks from SConnection
299
300void VNCSConnectionST::versionReceived() {
301 CharArray address(sock->getPeerAddress());
302 if ((rfb::Server::blacklistLevel == 1) &&
303 server->blHosts->isBlackmarked(address.buf)) {
304 server->connectionsLog.error("blacklisted: %s", address.buf);
305 throwConnFailedException("Too many security failures");
306 }
307}
308
309SSecurity* VNCSConnectionST::getSSecurity(int secType) {
310 if (!server->securityFactory)
311 throw rdr::Exception("no SSecurityFactory registered!");
312 return server->securityFactory->getSSecurity(secType, reverseConnection);
313}
314
315void VNCSConnectionST::authSuccess()
316{
317 lastEventTime = time(0);
318
319 // - Authentication succeeded - clear from blacklist
320 CharArray name; name.buf = sock->getPeerAddress();
321 server->blHosts->clearBlackmark(name.buf);
322
323 server->startDesktop();
324
325 // - Set the connection parameters appropriately
326 cp.width = server->pb->width();
327 cp.height = server->pb->height();
328 cp.setName(server->getName());
329
330 // - Set the default pixel format
331 cp.setPF(server->pb->getPF());
332 char buffer[256];
333 cp.pf().print(buffer, 256);
334 vlog.info("Server default pixel format %s", buffer);
335 image_getter.init(server->pb, cp.pf(), 0);
336
337 // - Mark the entire display as "dirty"
338 updates.add_changed(server->pb->getRect());
339}
340
341void VNCSConnectionST::queryConnection(const char* userName)
342{
343 // - Does the client have the right to bypass the query?
344 if (reverseConnection || !rfb::Server::queryConnect ||
345 (accessRights & AccessNoQuery))
346 {
347 approveConnection(true);
348 return;
349 }
350
351 CharArray reason;
352 VNCServerST::queryResult qr = server->queryConnection(sock, userName,
353 &reason.buf);
354 if (qr == VNCServerST::PENDING) return;
355 approveConnection(qr == VNCServerST::ACCEPT, reason.buf);
356}
357
358void VNCSConnectionST::clientInit(bool shared)
359{
360 lastEventTime = time(0);
361 if (rfb::Server::alwaysShared || reverseConnection) shared = true;
362 if (rfb::Server::neverShared) shared = false;
363 if (!shared) {
364 if (rfb::Server::disconnectClients) {
365 // - Close all the other connected clients
366 vlog.debug("non-shared connection - closing clients");
367 server->closeClients("Non-shared connection requested", getSock());
368 } else {
369 // - Refuse this connection if there are existing clients, in addition to this one
370 if (server->authClientCount() > 1) {
371 close("Server is already in use");
372 return;
373 }
374 }
375 }
376 SConnection::clientInit(shared);
377}
378
379void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
380{
381 SConnection::setPixelFormat(pf);
382 char buffer[256];
383 pf.print(buffer, 256);
384 vlog.info("Client pixel format %s", buffer);
385 image_getter.init(server->pb, pf, writer());
386 setCursor();
387}
388
389void VNCSConnectionST::pointerEvent(int x, int y, int buttonMask)
390{
391 pointerEventTime = lastEventTime = time(0);
392 if (!(accessRights & AccessPtrEvents)) return;
393 if (!rfb::Server::acceptPointerEvents) return;
394 if (!server->pointerClient || server->pointerClient == this) {
395 pointerEventPos = Point(x, y);
396 if (buttonMask)
397 server->pointerClient = this;
398 else
399 server->pointerClient = 0;
400 server->desktop->pointerEvent(pointerEventPos, buttonMask);
401 }
402}
403
404
405class VNCSConnectionSTShiftPresser {
406public:
407 VNCSConnectionSTShiftPresser(SDesktop* desktop_)
408 : desktop(desktop_), pressed(false) {}
409 ~VNCSConnectionSTShiftPresser() {
410 if (pressed) { desktop->keyEvent(XK_Shift_L, false); }
411 }
412 void press() {
413 desktop->keyEvent(XK_Shift_L, true);
414 pressed = true;
415 }
416 SDesktop* desktop;
417 bool pressed;
418};
419
420// keyEvent() - record in the pressedKeys which keys were pressed. Allow
421// multiple down events (for autorepeat), but only allow a single up event.
422void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
423 lastEventTime = time(0);
424 if (!(accessRights & AccessKeyEvents)) return;
425 if (!rfb::Server::acceptKeyEvents) return;
426
427 // Turn ISO_Left_Tab into shifted Tab.
428 VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
429 if (key == XK_ISO_Left_Tab) {
430 if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
431 pressedKeys.find(XK_Shift_R) == pressedKeys.end())
432 shiftPresser.press();
433 key = XK_Tab;
434 }
435
436 if (down) {
437 pressedKeys.insert(key);
438 } else {
439 if (!pressedKeys.erase(key)) return;
440 }
441 server->desktop->keyEvent(key, down);
442}
443
444void VNCSConnectionST::clientCutText(const char* str, int len)
445{
446 if (!(accessRights & AccessCutText)) return;
447 if (!rfb::Server::acceptCutText) return;
448 server->desktop->clientCutText(str, len);
449}
450
451void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
452{
453 if (!(accessRights & AccessView)) return;
454
455 SConnection::framebufferUpdateRequest(r, incremental);
456
457 Region reqRgn(r);
458 requested.assign_union(reqRgn);
459
460 if (!incremental) {
461 // Non-incremental update - treat as if area requested has changed
462 updates.add_changed(reqRgn);
463 server->comparer->add_changed(reqRgn);
464 }
465
466 writeFramebufferUpdate();
467}
468
469void VNCSConnectionST::setInitialColourMap()
470{
471 setColourMapEntries(0, 0);
472}
473
474// supportsLocalCursor() is called whenever the status of
475// cp.supportsLocalCursor has changed. If the client does now support local
476// cursor, we make sure that the old server-side rendered cursor is cleaned up
477// and the cursor is sent to the client.
478
479void VNCSConnectionST::supportsLocalCursor()
480{
Peter Åstrand1e9d32f2005-02-16 13:00:38 +0000481 if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000482 removeRenderedCursor = true;
483 drawRenderedCursor = false;
484 setCursor();
485 }
486}
487
488void VNCSConnectionST::writeSetCursorCallback()
489{
Peter Åstrand1e9d32f2005-02-16 13:00:38 +0000490 if (cp.supportsLocalXCursor) {
491 Pixel pix0, pix1;
492 rdr::U8Array bitmap(server->cursor.getBitmap(&pix0, &pix1));
493 if (bitmap.buf) {
494 // The client supports XCursor and the cursor only has two
495 // colors. Use the XCursor encoding.
496 writer()->writeSetXCursor(server->cursor.width(),
497 server->cursor.height(),
498 server->cursor.hotspot.x,
499 server->cursor.hotspot.y,
500 bitmap.buf, server->cursor.mask.buf);
501 return;
502 } else {
503 // More than two colors
504 if (!cp.supportsLocalCursor) {
505 // FIXME: We could reduce to two colors.
506 vlog.info("Unable to send multicolor cursor: RichCursor not supported by client");
507 return;
508 }
509 }
510 }
511
512 // Use RichCursor
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000513 rdr::U8* transData = writer()->getImageBuf(server->cursor.area());
514 image_getter.translatePixels(server->cursor.data, transData,
Peter Åstrand1e9d32f2005-02-16 13:00:38 +0000515 server->cursor.area());
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000516 writer()->writeSetCursor(server->cursor.width(),
Peter Åstrand1e9d32f2005-02-16 13:00:38 +0000517 server->cursor.height(),
518 server->cursor.hotspot.x,
519 server->cursor.hotspot.y,
520 transData, server->cursor.mask.buf);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000521}
522
523
524void VNCSConnectionST::writeFramebufferUpdate()
525{
526 if (state() != RFBSTATE_NORMAL || requested.is_empty()) return;
527
528 server->checkUpdate();
529
530 // If the previous position of the rendered cursor overlaps the source of the
531 // copy, then when the copy happens the corresponding rectangle in the
532 // destination will be wrong, so add it to the changed region.
533
534 if (!updates.get_copied().is_empty() && !renderedCursorRect.is_empty()) {
535 Rect bogusCopiedCursor = (renderedCursorRect.translate(updates.get_delta())
536 .intersect(server->pb->getRect()));
537 if (!updates.get_copied().intersect(bogusCopiedCursor).is_empty()) {
538 updates.add_changed(bogusCopiedCursor);
539 }
540 }
541
542 // If we need to remove the old rendered cursor, just add the rectangle to
543 // the changed region.
544
545 if (removeRenderedCursor) {
546 updates.add_changed(renderedCursorRect);
547 renderedCursorRect.clear();
548 removeRenderedCursor = false;
549 }
550
551 // Return if there is nothing to send the client.
552
553 if (updates.is_empty() && !writer()->needFakeUpdate() && !drawRenderedCursor)
554 return;
555
556 // If the client needs a server-side rendered cursor, work out the cursor
557 // rectangle. If it's empty then don't bother drawing it, but if it overlaps
558 // with the update region, we need to draw the rendered cursor regardless of
559 // whether it has changed.
560
561 if (needRenderedCursor()) {
562 renderedCursorRect
563 = (server->renderedCursor.getRect(server->renderedCursorTL)
564 .intersect(requested.get_bounding_rect()));
565
566 if (renderedCursorRect.is_empty()) {
567 drawRenderedCursor = false;
568 } else if (!updates.get_changed().union_(updates.get_copied())
569 .intersect(renderedCursorRect).is_empty()) {
570 drawRenderedCursor = true;
571 }
572
573 // We could remove the new cursor rect from updates here. It's not clear
574 // whether this is worth it. If we do remove it, then we won't draw over
575 // the same bit of screen twice, but we have the overhead of a more complex
576 // region.
577
578 //if (drawRenderedCursor)
579 // updates.subtract(renderedCursorRect);
580 }
581
582 UpdateInfo update;
583 updates.enable_copyrect(cp.useCopyRect);
584 updates.get_update(&update, requested);
585 if (!update.is_empty() || writer()->needFakeUpdate() || drawRenderedCursor) {
Peter Åstrand016dc192005-02-10 16:41:06 +0000586 // Compute the number of rectangles. Tight encoder makes the things more
587 // complicated as compared to the original RealVNC.
588 writer()->setupCurrentEncoder();
589 int nRects = update.copied.numRects() + (drawRenderedCursor ? 1 : 0);
590 std::vector<Rect> rects;
591 std::vector<Rect>::const_iterator i;
592 update.changed.get_rects(&rects);
593 for (i = rects.begin(); i != rects.end(); i++) {
594 if (i->width() && i->height())
595 nRects += writer()->getNumRects(*i);
596 }
597
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000598 writer()->writeFramebufferUpdateStart(nRects);
599 Region updatedRegion;
600 writer()->writeRects(update, &image_getter, &updatedRegion);
601 updates.subtract(updatedRegion);
602 if (drawRenderedCursor)
603 writeRenderedCursorRect();
604 writer()->writeFramebufferUpdateEnd();
605 requested.clear();
606 }
607}
608
609
610// writeRenderedCursorRect() writes a single rectangle drawing the rendered
611// cursor on the client.
612
613void VNCSConnectionST::writeRenderedCursorRect()
614{
615 image_getter.setPixelBuffer(&server->renderedCursor);
616 image_getter.setOffset(server->renderedCursorTL);
617
618 Rect actual;
619 writer()->writeRect(renderedCursorRect, &image_getter, &actual);
620
621 image_getter.setPixelBuffer(server->pb);
622 image_getter.setOffset(Point(0,0));
623
624 drawRenderedCursor = false;
625}
626
627void VNCSConnectionST::setColourMapEntries(int firstColour, int nColours)
628{
629 if (!readyForSetColourMapEntries) return;
630 if (server->pb->getPF().trueColour) return;
631
632 image_getter.setColourMapEntries(firstColour, nColours, writer());
633
634 if (cp.pf().trueColour) {
635 updates.add_changed(server->pb->getRect());
636 }
637}
638
639
640// setCursor() is called whenever the cursor has changed shape or pixel format.
641// If the client supports local cursor then it will arrange for the cursor to
642// be sent to the client.
643
644void VNCSConnectionST::setCursor()
645{
646 if (state() != RFBSTATE_NORMAL || !cp.supportsLocalCursor) return;
647 writer()->cursorChange(this);
648 if (writer()->needFakeUpdate())
649 writeFramebufferUpdate();
650}
651
652void VNCSConnectionST::setSocketTimeouts()
653{
654 int timeoutms = rfb::Server::clientWaitTimeMillis;
655 if (timeoutms == 0 || timeoutms > rfb::Server::idleTimeout * 1000) {
656 timeoutms = rfb::Server::idleTimeout * 1000;
657 if (timeoutms == 0)
658 timeoutms = -1;
659 }
660 sock->inStream().setTimeout(timeoutms);
661 sock->outStream().setTimeout(timeoutms);
662}