1 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
2  * Copyright 2009-2019 Pierre Ossman for Cendio AB
3  * Copyright 2014 Brian P. Hinz
4  *
5  * This 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  * This software 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 this software; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
18  * USA.
19  */
20 //
21 // XserverDesktop.cxx
22 //
23 
24 #include <assert.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <strings.h>
29 #include <unistd.h>
30 #include <pwd.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <sys/utsname.h>
35 
36 #include <network/Socket.h>
37 #include <rfb/Exception.h>
38 #include <rfb/VNCServerST.h>
39 #include <rfb/LogWriter.h>
40 #include <rfb/Configuration.h>
41 #include <rfb/ServerCore.h>
42 
43 #include "XserverDesktop.h"
44 #include "vncBlockHandler.h"
45 #include "vncExtInit.h"
46 #include "vncHooks.h"
47 #include "vncSelection.h"
48 #include "XorgGlue.h"
49 #include "vncInput.h"
50 
51 extern "C" {
52 void vncSetGlueContext(int screenIndex);
53 }
54 
55 using namespace rfb;
56 using namespace network;
57 
58 static LogWriter vlog("XserverDesktop");
59 
60 BoolParameter rawKeyboard("RawKeyboard",
61                           "Send keyboard events straight through and "
62                           "avoid mapping them to the current keyboard "
63                           "layout", false);
64 IntParameter queryConnectTimeout("QueryConnectTimeout",
65                                  "Number of seconds to show the "
66                                  "Accept Connection dialog before "
67                                  "rejecting the connection",
68                                  10);
69 
70 
XserverDesktop(int screenIndex_,std::list<network::SocketListener * > listeners_,const char * name,const rfb::PixelFormat & pf,int width,int height,void * fbptr,int stride_)71 XserverDesktop::XserverDesktop(int screenIndex_,
72                                std::list<network::SocketListener*> listeners_,
73                                const char* name, const rfb::PixelFormat &pf,
74                                int width, int height,
75                                void* fbptr, int stride_)
76   : screenIndex(screenIndex_),
77     server(0), listeners(listeners_),
78     shadowFramebuffer(NULL),
79     queryConnectId(0), queryConnectTimer(this)
80 {
81   format = pf;
82 
83   server = new VNCServerST(name, this);
84   setFramebuffer(width, height, fbptr, stride_);
85 
86   for (std::list<SocketListener*>::iterator i = listeners.begin();
87        i != listeners.end();
88        i++) {
89     vncSetNotifyFd((*i)->getFd(), screenIndex, true, false);
90   }
91 }
92 
~XserverDesktop()93 XserverDesktop::~XserverDesktop()
94 {
95   while (!listeners.empty()) {
96     vncRemoveNotifyFd(listeners.back()->getFd());
97     delete listeners.back();
98     listeners.pop_back();
99   }
100   if (shadowFramebuffer)
101     delete [] shadowFramebuffer;
102   delete server;
103 }
104 
blockUpdates()105 void XserverDesktop::blockUpdates()
106 {
107   server->blockUpdates();
108 }
109 
unblockUpdates()110 void XserverDesktop::unblockUpdates()
111 {
112   server->unblockUpdates();
113 }
114 
setFramebuffer(int w,int h,void * fbptr,int stride_)115 void XserverDesktop::setFramebuffer(int w, int h, void* fbptr, int stride_)
116 {
117   ScreenSet layout;
118 
119   if (shadowFramebuffer) {
120     delete [] shadowFramebuffer;
121     shadowFramebuffer = NULL;
122   }
123 
124   if (!fbptr) {
125     shadowFramebuffer = new rdr::U8[w * h * (format.bpp/8)];
126     fbptr = shadowFramebuffer;
127     stride_ = w;
128   }
129 
130   setBuffer(w, h, (rdr::U8*)fbptr, stride_);
131 
132   vncSetGlueContext(screenIndex);
133   layout = ::computeScreenLayout(&outputIdMap);
134 
135   server->setPixelBuffer(this, layout);
136 }
137 
refreshScreenLayout()138 void XserverDesktop::refreshScreenLayout()
139 {
140   vncSetGlueContext(screenIndex);
141   server->setScreenLayout(::computeScreenLayout(&outputIdMap));
142 }
143 
start(rfb::VNCServer * vs)144 void XserverDesktop::start(rfb::VNCServer* vs)
145 {
146   // We already own the server object, and we always keep it in a
147   // ready state
148   assert(vs == server);
149 }
150 
stop()151 void XserverDesktop::stop()
152 {
153 }
154 
queryConnection(network::Socket * sock,const char * userName)155 void XserverDesktop::queryConnection(network::Socket* sock,
156                                      const char* userName)
157 {
158   int count;
159 
160   if (queryConnectTimer.isStarted()) {
161     server->approveConnection(sock, false, "Another connection is currently being queried.");
162     return;
163   }
164 
165   count = vncNotifyQueryConnect();
166   if (count == 0) {
167     server->approveConnection(sock, false, "Unable to query the local user to accept the connection.");
168     return;
169   }
170 
171   queryConnectAddress.replaceBuf(sock->getPeerAddress());
172   if (!userName)
173     userName = "(anonymous)";
174   queryConnectUsername.replaceBuf(strDup(userName));
175   queryConnectId = (uint32_t)(intptr_t)sock;
176   queryConnectSocket = sock;
177 
178   queryConnectTimer.start(queryConnectTimeout * 1000);
179 }
180 
requestClipboard()181 void XserverDesktop::requestClipboard()
182 {
183   try {
184     server->requestClipboard();
185   } catch (rdr::Exception& e) {
186     vlog.error("XserverDesktop::requestClipboard: %s",e.str());
187   }
188 }
189 
announceClipboard(bool available)190 void XserverDesktop::announceClipboard(bool available)
191 {
192   try {
193     server->announceClipboard(available);
194   } catch (rdr::Exception& e) {
195     vlog.error("XserverDesktop::announceClipboard: %s",e.str());
196   }
197 }
198 
sendClipboardData(const char * data_)199 void XserverDesktop::sendClipboardData(const char* data_)
200 {
201   try {
202     server->sendClipboardData(data_);
203   } catch (rdr::Exception& e) {
204     vlog.error("XserverDesktop::sendClipboardData: %s",e.str());
205   }
206 }
207 
bell()208 void XserverDesktop::bell()
209 {
210   server->bell();
211 }
212 
setLEDState(unsigned int state)213 void XserverDesktop::setLEDState(unsigned int state)
214 {
215   server->setLEDState(state);
216 }
217 
setDesktopName(const char * name)218 void XserverDesktop::setDesktopName(const char* name)
219 {
220   try {
221     server->setName(name);
222   } catch (rdr::Exception& e) {
223     vlog.error("XserverDesktop::setDesktopName: %s",e.str());
224   }
225 }
226 
setCursor(int width,int height,int hotX,int hotY,const unsigned char * rgbaData)227 void XserverDesktop::setCursor(int width, int height, int hotX, int hotY,
228                                const unsigned char *rgbaData)
229 {
230   rdr::U8* cursorData;
231 
232   rdr::U8 *out;
233   const unsigned char *in;
234 
235   cursorData = new rdr::U8[width * height * 4];
236 
237   // Un-premultiply alpha
238   in = rgbaData;
239   out = cursorData;
240   for (int y = 0; y < height; y++) {
241     for (int x = 0; x < width; x++) {
242       rdr::U8 alpha;
243 
244       alpha = in[3];
245       if (alpha == 0)
246         alpha = 1; // Avoid division by zero
247 
248       *out++ = (unsigned)*in++ * 255/alpha;
249       *out++ = (unsigned)*in++ * 255/alpha;
250       *out++ = (unsigned)*in++ * 255/alpha;
251       *out++ = *in++;
252     }
253   }
254 
255   try {
256     server->setCursor(width, height, Point(hotX, hotY), cursorData);
257   } catch (rdr::Exception& e) {
258     vlog.error("XserverDesktop::setCursor: %s",e.str());
259   }
260 
261   delete [] cursorData;
262 }
263 
setCursorPos(int x,int y,bool warped)264 void XserverDesktop::setCursorPos(int x, int y, bool warped)
265 {
266   try {
267     server->setCursorPos(Point(x, y), warped);
268   } catch (rdr::Exception& e) {
269     vlog.error("XserverDesktop::setCursorPos: %s",e.str());
270   }
271 }
272 
add_changed(const rfb::Region & region)273 void XserverDesktop::add_changed(const rfb::Region &region)
274 {
275   try {
276     server->add_changed(region);
277   } catch (rdr::Exception& e) {
278     vlog.error("XserverDesktop::add_changed: %s",e.str());
279   }
280 }
281 
add_copied(const rfb::Region & dest,const rfb::Point & delta)282 void XserverDesktop::add_copied(const rfb::Region &dest, const rfb::Point &delta)
283 {
284   try {
285     server->add_copied(dest, delta);
286   } catch (rdr::Exception& e) {
287     vlog.error("XserverDesktop::add_copied: %s",e.str());
288   }
289 }
290 
handleSocketEvent(int fd,bool read,bool write)291 void XserverDesktop::handleSocketEvent(int fd, bool read, bool write)
292 {
293   try {
294     if (read) {
295       if (handleListenerEvent(fd, &listeners, server))
296         return;
297     }
298 
299     if (handleSocketEvent(fd, server, read, write))
300       return;
301 
302     vlog.error("Cannot find file descriptor for socket event");
303   } catch (rdr::Exception& e) {
304     vlog.error("XserverDesktop::handleSocketEvent: %s",e.str());
305   }
306 }
307 
handleListenerEvent(int fd,std::list<SocketListener * > * sockets,SocketServer * sockserv)308 bool XserverDesktop::handleListenerEvent(int fd,
309                                          std::list<SocketListener*>* sockets,
310                                          SocketServer* sockserv)
311 {
312   std::list<SocketListener*>::iterator i;
313 
314   for (i = sockets->begin(); i != sockets->end(); i++) {
315     if ((*i)->getFd() == fd)
316       break;
317   }
318 
319   if (i == sockets->end())
320     return false;
321 
322   Socket* sock = (*i)->accept();
323   vlog.debug("new client, sock %d", sock->getFd());
324   sockserv->addSocket(sock);
325   vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
326 
327   return true;
328 }
329 
handleSocketEvent(int fd,SocketServer * sockserv,bool read,bool write)330 bool XserverDesktop::handleSocketEvent(int fd,
331                                        SocketServer* sockserv,
332                                        bool read, bool write)
333 {
334   std::list<Socket*> sockets;
335   std::list<Socket*>::iterator i;
336 
337   sockserv->getSockets(&sockets);
338   for (i = sockets.begin(); i != sockets.end(); i++) {
339     if ((*i)->getFd() == fd)
340       break;
341   }
342 
343   if (i == sockets.end())
344     return false;
345 
346   if (read)
347     sockserv->processSocketReadEvent(*i);
348 
349   if (write)
350     sockserv->processSocketWriteEvent(*i);
351 
352   return true;
353 }
354 
blockHandler(int * timeout)355 void XserverDesktop::blockHandler(int* timeout)
356 {
357   // We don't have a good callback for when we can init input devices[1],
358   // so we abuse the fact that this routine will be called first thing
359   // once the dix is done initialising.
360   // [1] Technically Xvnc has InitInput(), but libvnc.so has nothing.
361   vncInitInputDevice();
362 
363   try {
364     std::list<Socket*> sockets;
365     std::list<Socket*>::iterator i;
366     server->getSockets(&sockets);
367     for (i = sockets.begin(); i != sockets.end(); i++) {
368       int fd = (*i)->getFd();
369       if ((*i)->isShutdown()) {
370         vlog.debug("client gone, sock %d",fd);
371         vncRemoveNotifyFd(fd);
372         server->removeSocket(*i);
373         vncClientGone(fd);
374         delete (*i);
375       } else {
376         /* Update existing NotifyFD to listen for write (or not) */
377         vncSetNotifyFd(fd, screenIndex, true, (*i)->outStream().hasBufferedData());
378       }
379     }
380 
381     // We are responsible for propagating mouse movement between clients
382     int cursorX, cursorY;
383     vncGetPointerPos(&cursorX, &cursorY);
384     cursorX -= vncGetScreenX(screenIndex);
385     cursorY -= vncGetScreenY(screenIndex);
386     if (oldCursorPos.x != cursorX || oldCursorPos.y != cursorY) {
387       oldCursorPos.x = cursorX;
388       oldCursorPos.y = cursorY;
389       server->setCursorPos(oldCursorPos, false);
390     }
391 
392     // Trigger timers and check when the next will expire
393     int nextTimeout = Timer::checkTimeouts();
394     if (nextTimeout > 0 && (*timeout == -1 || nextTimeout < *timeout))
395       *timeout = nextTimeout;
396   } catch (rdr::Exception& e) {
397     vlog.error("XserverDesktop::blockHandler: %s",e.str());
398   }
399 }
400 
addClient(Socket * sock,bool reverse)401 void XserverDesktop::addClient(Socket* sock, bool reverse)
402 {
403   vlog.debug("new client, sock %d reverse %d",sock->getFd(),reverse);
404   server->addSocket(sock, reverse);
405   vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
406 }
407 
disconnectClients()408 void XserverDesktop::disconnectClients()
409 {
410   vlog.debug("disconnecting all clients");
411   return server->closeClients("Disconnection from server end");
412 }
413 
414 
getQueryConnect(uint32_t * opaqueId,const char ** address,const char ** username,int * timeout)415 void XserverDesktop::getQueryConnect(uint32_t* opaqueId,
416                                      const char** address,
417                                      const char** username,
418                                      int *timeout)
419 {
420   *opaqueId = queryConnectId;
421 
422   if (!queryConnectTimer.isStarted()) {
423     *address = "";
424     *username = "";
425     *timeout = 0;
426   } else {
427     *address = queryConnectAddress.buf;
428     *username = queryConnectUsername.buf;
429     *timeout = queryConnectTimeout;
430   }
431 }
432 
approveConnection(uint32_t opaqueId,bool accept,const char * rejectMsg)433 void XserverDesktop::approveConnection(uint32_t opaqueId, bool accept,
434                                        const char* rejectMsg)
435 {
436   if (queryConnectId == opaqueId) {
437     server->approveConnection(queryConnectSocket, accept, rejectMsg);
438     queryConnectId = 0;
439     queryConnectTimer.stop();
440   }
441 }
442 
443 ///////////////////////////////////////////////////////////////////////////
444 //
445 // SDesktop callbacks
446 
447 
terminate()448 void XserverDesktop::terminate()
449 {
450   kill(getpid(), SIGTERM);
451 }
452 
pointerEvent(const Point & pos,int buttonMask)453 void XserverDesktop::pointerEvent(const Point& pos, int buttonMask)
454 {
455   vncPointerMove(pos.x + vncGetScreenX(screenIndex),
456                  pos.y + vncGetScreenY(screenIndex));
457   vncPointerButtonAction(buttonMask);
458 }
459 
setScreenLayout(int fb_width,int fb_height,const rfb::ScreenSet & layout)460 unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height,
461                                              const rfb::ScreenSet& layout)
462 {
463   unsigned int result;
464 
465   char buffer[2048];
466   vlog.debug("Got request for framebuffer resize to %dx%d",
467              fb_width, fb_height);
468   layout.print(buffer, sizeof(buffer));
469   vlog.debug("%s", buffer);
470 
471   vncSetGlueContext(screenIndex);
472   result = ::setScreenLayout(fb_width, fb_height, layout, &outputIdMap);
473 
474   // Explicitly update the server state with the result as there
475   // can be corner cases where we don't get feedback from the X core
476   refreshScreenLayout();
477 
478   return result;
479 }
480 
handleClipboardRequest()481 void XserverDesktop::handleClipboardRequest()
482 {
483   vncHandleClipboardRequest();
484 }
485 
handleClipboardAnnounce(bool available)486 void XserverDesktop::handleClipboardAnnounce(bool available)
487 {
488   vncHandleClipboardAnnounce(available);
489 }
490 
handleClipboardData(const char * data_)491 void XserverDesktop::handleClipboardData(const char* data_)
492 {
493   vncHandleClipboardData(data_);
494 }
495 
grabRegion(const rfb::Region & region)496 void XserverDesktop::grabRegion(const rfb::Region& region)
497 {
498   if (shadowFramebuffer == NULL)
499     return;
500 
501   std::vector<rfb::Rect> rects;
502   std::vector<rfb::Rect>::iterator i;
503   region.get_rects(&rects);
504   for (i = rects.begin(); i != rects.end(); i++) {
505     rdr::U8 *buffer;
506     int bufStride;
507 
508     buffer = getBufferRW(*i, &bufStride);
509     vncGetScreenImage(screenIndex, i->tl.x, i->tl.y, i->width(), i->height(),
510                       (char*)buffer, bufStride * format.bpp/8);
511     commitBufferRW(*i);
512   }
513 }
514 
keyEvent(rdr::U32 keysym,rdr::U32 keycode,bool down)515 void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
516 {
517   if (!rawKeyboard)
518     keycode = 0;
519 
520   vncKeyboardEvent(keysym, keycode, down);
521 }
522 
handleTimeout(Timer * t)523 bool XserverDesktop::handleTimeout(Timer* t)
524 {
525   if (t == &queryConnectTimer) {
526     server->approveConnection(queryConnectSocket, false,
527                               "The attempt to prompt the user to "
528                               "accept the connection failed");
529   }
530 
531   return false;
532 }
533