/* Copyright (C) 2007-2011 Proxmox Server Solutions GmbH Copyright: vzdump is under GNU GPL, the GNU General Public License. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Author: Dietmar Maurer */ #include #include #include #include #include #include #include #include #include #include #include #include /* for openpty and forkpty */ #include #include #include #include #include #include #include "vncterm.h" #include #include /* define this for debugging */ //#define DEBUG char *auth_path = "/"; char *auth_perm = "Sys.Console"; uint16_t screen_width = 744; uint16_t screen_height = 400; int use_x509 = 1; extern int wcwidth (wchar_t wc); unsigned char *fontdata; #define FONTFILE "/usr/share/vncterm/font.data" #define GLYPHLINES 16 static char * urlencode(char *buf, const char *value) { static const char *hexchar = "0123456789abcdef"; char *p = buf; int i; int l = strlen(value); for (i = 0; i < l; i++) { char c = value[i]; if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) { *p++ = c; } else if (c == 32) { *p++ = '+'; } else { *p++ = '%'; *p++ = hexchar[c >> 4]; *p++ = hexchar[c & 15]; } } *p = 0; return p; } static int pve_auth_verify(const char *clientip, const char *username, const char *passwd) { struct sockaddr_in server; int sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) { perror("pve_auth_verify: socket failed"); return -1; } struct hostent *he; if ((he = gethostbyname("localhost")) == NULL) { fprintf(stderr, "pve_auth_verify: error resolving hostname\n"); goto err; } memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length); server.sin_family = AF_INET; server.sin_port = htons(85); if (connect(sfd, (struct sockaddr *)&server, sizeof(server))) { perror("pve_auth_verify: error connecting to server"); goto err; } char buf[8192]; char form[8192]; char *p = form; p = urlencode(p, "username"); *p++ = '='; p = urlencode(p, username); *p++ = '&'; p = urlencode(p, "password"); *p++ = '='; p = urlencode(p, passwd); *p++ = '&'; p = urlencode(p, "path"); *p++ = '='; p = urlencode(p, auth_path); *p++ = '&'; p = urlencode(p, "privs"); *p++ = '='; p = urlencode(p, auth_perm); sprintf(buf, "POST /api2/json/access/ticket HTTP/1.1\n" "Host: localhost:85\n" "Connection: close\n" "PVEClientIP: %s\n" "Content-Type: application/x-www-form-urlencoded\n" "Content-Length: %zd\n\n%s\n", clientip, strlen(form), form); ssize_t len = strlen(buf); ssize_t sb = send(sfd, buf, len, 0); if (sb < 0) { perror("pve_auth_verify: send failed"); goto err; } if (sb != len) { fprintf(stderr, "pve_auth_verify: partial send error\n"); goto err; } len = recv(sfd, buf, sizeof(buf) - 1, 0); if (len < 0) { perror("pve_auth_verify: recv failed"); goto err; } buf[len] = 0; //printf("DATA:%s\n", buf); shutdown(sfd, SHUT_RDWR); return strncmp(buf, "HTTP/1.1 200 OK", 15); err: shutdown(sfd, SHUT_RDWR); return -1; } #ifdef DEBUG static void vnc_debug_gnutls_log(int level, const char* str) { fprintf(stderr, "%d %s", level, str); } #endif #define DH_BITS 2048 static gnutls_dh_params_t dh_params; typedef struct { gnutls_session_t session; } tls_client_t; static ssize_t vnc_tls_push( gnutls_transport_ptr_t transport, const void *data, size_t len) { rfbClientPtr cl = (rfbClientPtr)transport; int n; retry: n = send(cl->sock, data, len, 0); if (n < 0) { if (errno == EINTR) goto retry; return -1; } return n; } static ssize_t vnc_tls_pull( gnutls_transport_ptr_t transport, void *data, size_t len) { rfbClientPtr cl = (rfbClientPtr)transport; int n; retry: n = recv(cl->sock, data, len, 0); if (n < 0) { if (errno == EINTR) goto retry; return -1; } return n; } ssize_t vnc_tls_read(rfbClientPtr cl, void *buf, size_t count) { tls_client_t *sd = (tls_client_t *)cl->clientData; int ret = gnutls_read(sd->session, buf, count); if (ret < 0) { if (ret == GNUTLS_E_AGAIN) errno = EAGAIN; else errno = EIO; ret = -1; } return ret; } ssize_t vnc_tls_write(rfbClientPtr cl, void *buf, size_t count) { tls_client_t *sd = (tls_client_t *)cl->clientData; int ret = gnutls_write(sd->session, buf, count); if (ret < 0) { if (ret == GNUTLS_E_AGAIN) errno = EAGAIN; else errno = EIO; ret = -1; } return ret; } static gnutls_anon_server_credentials tls_initialize_anon_cred(void) { gnutls_anon_server_credentials anon_cred; int ret; if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { rfbLog("can't allocate credentials: %s\n", gnutls_strerror(ret)); return NULL; } #if GNUTLS_VERSION_NUMBER >= 0x030506 gnutls_anon_set_server_known_dh_params(anon_cred, GNUTLS_SEC_PARAM_MEDIUM); #else gnutls_anon_set_server_dh_params(anon_cred, dh_params); #endif return anon_cred; } static gnutls_certificate_credentials_t tls_initialize_x509_cred(void) { gnutls_certificate_credentials_t x509_cred; int ret; /* Paths to x509 certs/keys */ char *x509cacert = "/etc/pve/pve-root-ca.pem"; char *x509cert = "/etc/pve/local/pve-ssl.pem"; char *x509key = "/etc/pve/local/pve-ssl.key"; if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { rfbLog("can't allocate credentials: %s\n", gnutls_strerror(ret)); return NULL; } if ((ret = gnutls_certificate_set_x509_trust_file (x509_cred, x509cacert, GNUTLS_X509_FMT_PEM)) < 0) { rfbLog("can't load CA certificate: %s\n", gnutls_strerror(ret)); gnutls_certificate_free_credentials(x509_cred); return NULL; } if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, x509cert, x509key, GNUTLS_X509_FMT_PEM)) < 0) { rfbLog("can't load certificate & key: %s\n", gnutls_strerror(ret)); gnutls_certificate_free_credentials(x509_cred); return NULL; } #if GNUTLS_VERSION_NUMBER >= 0x030506 /* only available since GnuTLS 3.5.6, on previous versions see * gnutls_certificate_set_dh_params(). */ gnutls_certificate_set_known_dh_params(x509_cred, GNUTLS_SEC_PARAM_MEDIUM); #else gnutls_certificate_set_dh_params (x509_cred, dh_params); #endif return x509_cred; } /* rfb tls security handler */ #define rfbSecTypeVencrypt 19 #define rfbVencryptTlsPlain 259 #define rfbVencryptX509Plain 262 void rfbEncodeU32(char *buf, uint32_t value) { buf[0] = (value >> 24) & 0xFF; buf[1] = (value >> 16) & 0xFF; buf[2] = (value >> 8) & 0xFF; buf[3] = value & 0xFF; } uint32_t rfbDecodeU32(char *data, size_t offset) { return ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } static void vencrypt_subauth_plain(rfbClientPtr cl) { const char *err = NULL; char buf[4096]; int n; char clientip[INET6_ADDRSTRLEN]; clientip[0] = 0; struct sockaddr_in client; socklen_t addrlen = sizeof(client); if (getpeername(cl->sock, &client, &addrlen) == 0) { inet_ntop(client.sin_family, &client.sin_addr, clientip, sizeof(clientip)); } if ((n = rfbReadExact(cl, buf, 8)) <= 0) { err = n ? "read failed" : "client gone"; goto err; } uint32_t ulen = rfbDecodeU32(buf, 0); uint32_t pwlen = rfbDecodeU32(buf, 4); if (!ulen) { err = "No User name."; goto err; } if (ulen >= 255) { err = "User name too long."; goto err; } if (!pwlen) { err = "Password too short"; goto err; } if (pwlen >= 511) { err = "Password too long."; goto err; } if ((n = rfbReadExact(cl, buf, ulen)) <= 0) { err = n ? "read failed" : "client gone"; goto err; } buf[ulen] = 0; char *username = buf; char *passwd = buf + ulen + 1; if ((n = rfbReadExact(cl, passwd, pwlen)) <= 0) { err = n ? "read failed" : "client gone"; goto err; } passwd[pwlen] = 0; rfbLog("VencryptPlain: username: %s pw: %s\n", username, passwd); if (pve_auth_verify(clientip, username, passwd) == 0) { rfbEncodeU32(buf, 0); /* Accept auth completion */ rfbWriteExact(cl, buf, 4); cl->state = RFB_INITIALISATION; return; } err = "Authentication failed"; err: rfbLog("VencryptPlain: %s\n", err ? err : "no reason specified"); if (err) { rfbEncodeU32(buf, 1); /* Reject auth */ rfbWriteExact(cl, buf, 4); if (cl->protocolMinorVersion >= 8) { int elen = strlen(err); rfbEncodeU32(buf, elen); rfbWriteExact(cl, buf, 4); rfbWriteExact(cl, err, elen); } } rfbCloseClient(cl); return; } static void rfbVncAuthVencrypt(rfbClientPtr cl) { int ret; /* Send VeNCrypt version 0.2 */ char buf[256]; buf[0] = 0; buf[1] = 2; if (rfbWriteExact(cl, buf, 2) < 0) { rfbLogPerror("rfbVncAuthVencrypt: write"); rfbCloseClient(cl); return; } int n = rfbReadExact(cl, buf, 2); if (n <= 0) { if (n == 0) rfbLog("rfbVncAuthVencrypt: client gone\n"); else rfbLogPerror("rfbVncAuthVencrypt: read"); rfbCloseClient(cl); return; } if (buf[0] != 0 || buf[1] != 2) { rfbLog("Unsupported VeNCrypt protocol %d.%d\n", (int)buf[0], (int)buf[1]); buf[0] = 1; /* Reject version */ rfbWriteExact(cl, buf, 1); rfbCloseClient(cl); return; } /* Sending allowed auth */ int req_auth = use_x509 ? rfbVencryptX509Plain : rfbVencryptTlsPlain; buf[0] = 0; /* Accept version */ buf[1] = 1; /* number of sub auths */ rfbEncodeU32(buf+2, req_auth); if (rfbWriteExact(cl, buf, 6) < 0) { rfbLogPerror("rfbVncAuthVencrypt: write"); rfbCloseClient(cl); return; } n = rfbReadExact(cl, buf, 4); if (n <= 0) { if (n == 0) rfbLog("rfbVncAuthVencrypt: client gone\n"); else rfbLogPerror("rfbVncAuthVencrypt: read"); rfbCloseClient(cl); return; } int auth = rfbDecodeU32(buf, 0); if (auth != req_auth) { buf[0] = 1; /* Reject auth*/ rfbWriteExact(cl, buf, 1); rfbCloseClient(cl); return; } buf[0] = 1; /* Accept auth */ if (rfbWriteExact(cl, buf, 1) < 0) { rfbLogPerror("rfbVncAuthVencrypt: write"); rfbCloseClient(cl); return; } tls_client_t *sd = calloc(1, sizeof(tls_client_t)); if (sd->session == NULL) { if (gnutls_init(&sd->session, GNUTLS_SERVER) < 0) { rfbLog("gnutls_init failed\n"); rfbCloseClient(cl); return; } if ((ret = gnutls_set_default_priority(sd->session)) < 0) { rfbLog("gnutls_set_default_priority failed: %s\n", gnutls_strerror(ret)); sd->session = NULL; rfbCloseClient(cl); return; } static const char *priority_str_x509 = "NORMAL"; static const char *priority_str_anon = "NORMAL:+ANON-ECDH:+ANON-DH"; if ((ret = gnutls_priority_set_direct(sd->session, use_x509 ? priority_str_x509 : priority_str_anon, NULL)) < 0) { rfbLog("gnutls_priority_set_direct failed: %s\n", gnutls_strerror(ret)); sd->session = NULL; rfbCloseClient(cl); return; } if (use_x509) { gnutls_certificate_server_credentials x509_cred; if (!(x509_cred = tls_initialize_x509_cred())) { sd->session = NULL; rfbCloseClient(cl); return; } if (gnutls_credentials_set(sd->session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { sd->session = NULL; gnutls_certificate_free_credentials(x509_cred); rfbCloseClient(cl); return; } } else { gnutls_anon_server_credentials anon_cred; if (!(anon_cred = tls_initialize_anon_cred())) { sd->session = NULL; rfbCloseClient(cl); return; } if ((ret = gnutls_credentials_set(sd->session, GNUTLS_CRD_ANON, anon_cred)) < 0) { rfbLog("gnutls_credentials_set failed: %s\n", gnutls_strerror(ret)); gnutls_anon_free_server_credentials(anon_cred); sd->session = NULL; rfbCloseClient(cl); return; } } gnutls_transport_set_ptr(sd->session, (gnutls_transport_ptr_t)cl); gnutls_transport_set_push_function(sd->session, vnc_tls_push); gnutls_transport_set_pull_function(sd->session, vnc_tls_pull); } retry: if ((ret = gnutls_handshake(sd->session)) < 0) { if (!gnutls_error_is_fatal(ret)) { usleep(100000); goto retry; } rfbLog("rfbVncAuthVencrypt: handshake failed\n"); rfbCloseClient(cl); return; } /* set up TLS read/write hooks */ cl->clientData = sd; cl->sock_read_fn = &vnc_tls_read; cl->sock_write_fn = &vnc_tls_write; vencrypt_subauth_plain(cl); } static rfbSecurityHandler VncSecurityHandlerVencrypt = { rfbSecTypeVencrypt, rfbVncAuthVencrypt, NULL }; #define TERM "xterm" #define TERMIDCODE "[?1;2c" // vt100 ID #define CHECK_ARGC(argc,argv,i) if (i >= argc-1) { \ fprintf (stderr, "ERROR: not enough arguments for: %s\n", argv[i]); \ print_usage (NULL); \ exit(1); \ } /* these colours are from linux kernel drivers/char/vt.c */ static int idle_timeout = 1; unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15 }; /* the default colour table, for VGA+ colour systems */ int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa, 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff}; int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa, 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff}; int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa, 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff}; static void print_usage (const char *msg) { if (msg) { fprintf (stderr, "ERROR: %s\n", msg); } fprintf (stderr, "USAGE: vncterm [vncopts] [-c command [args]]\n"); } /* Convert UCS2 to UTF8 sequence, trailing zero */ static int ucs2_to_utf8 (unicode c, char *out) { if (c < 0x80) { out[0] = c; // 0******* out[1] = 0; return 1; } else if (c < 0x800) { out[0] = 0xc0 | (c >> 6); // 110***** 10****** out[1] = 0x80 | (c & 0x3f); out[2] = 0; return 2; } else { out[0] = 0xe0 | (c >> 12); // 1110**** 10****** 10****** out[1] = 0x80 | ((c >> 6) & 0x3f); out[2] = 0x80 | (c & 0x3f); out[3] = 0; return 3; } return 0; } static void rfb_draw_char (rfbScreenInfoPtr rfbScreen, int x, int y, unicode c, rfbPixel col, short width) { int i,j; unsigned char *data= fontdata + c*(GLYPHLINES*2); unsigned char d=*data; int rowstride=rfbScreen->paddedWidthInBytes; char *colour=(char*)&col; for(j = 0; j < GLYPHLINES; j++) { for(i = 0; i < 8*width; i++) { if ((i&7) == 0) { d=*data; data++; } if (d&0x80) *(rfbScreen->frameBuffer+(y+j)*rowstride+(x+i)) = *colour; d<<=1; } } } static void draw_char_at (vncTerm *vt, int x, int y, unicode ch, TextAttributes attrib, short width, unicode combiningglyph) { if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; } // non printable character if (width < 1) return; int rx = x*8; int ry = y*16; int rxe = x*8+8*width; int rye = y*16+16; int fg, bg; if (attrib.invers) { bg = attrib.fgcol; fg = attrib.bgcol; } else { bg = attrib.bgcol; fg = attrib.fgcol; } rfbFillRect (vt->screen, rx, ry, rxe, rye, bg); if (attrib.bold) { fg += 8; } // unsuported attributes = (attrib.blink || attrib.unvisible) rfb_draw_char (vt->screen, rx, ry, ch, fg, width); if (combiningglyph) { rfb_draw_char (vt->screen, rx, ry, combiningglyph, fg, 1); } if (attrib.uline) { rfbDrawLine (vt->screen, rx, ry + 14, rxe, ry + 14, fg); } rfbMarkRectAsModified (vt->screen, rx, ry, rxe, rye); } static void vncterm_update_xy (vncTerm *vt, int x, int y) { if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; } int y1 = (vt->y_base + y) % vt->total_height; int y2 = y1 - vt->y_displ; if (y2 < 0) { y2 += vt->total_height; } if (y2 < vt->height) { TextCell *c = &vt->cells[y1 * vt->width + x]; draw_char_at (vt, x, y2, c->ch, c->attrib, c->width, c->combiningglyph); } } static void vncterm_clear_xy (vncTerm *vt, int x, int y) { if (x < 0 || y < 0 || x >= vt->width || y >= vt->height) { return; } int y1 = (vt->y_base + y) % vt->total_height; int y2 = y1 - vt->y_displ; if (y2 < 0) { y2 += vt->total_height; } if (y2 < vt->height) { TextCell *c = &vt->cells[y1 * vt->width + x]; c->ch = ' '; c->attrib = vt->default_attrib; c->attrib.fgcol = vt->cur_attrib.fgcol; c->attrib.bgcol = vt->cur_attrib.bgcol; c->width = 1; c->combiningglyph = 0; draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph); } } static void vncterm_show_cursor (vncTerm *vt, int show) { int x = vt->cx; if (x >= vt->width) { x = vt->width - 1; } int y1 = (vt->y_base + vt->cy) % vt->total_height; int y = y1 - vt->y_displ; if (y < 0) { y += vt->total_height; } if (y < vt->height) { TextCell *c = &vt->cells[y1 * vt->width + x]; if (show) { TextAttributes attrib = vt->default_attrib; attrib.invers = !(attrib.invers); /* invert fg and bg */ draw_char_at (vt, x, y, c->ch, attrib, c->width, c->combiningglyph); } else { draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph); } } } static void vncterm_refresh (vncTerm *vt) { int x, y, y1; rfbFillRect (vt->screen, 0, 0, vt->maxx, vt->maxy, vt->default_attrib.bgcol); y1 = vt->y_displ; for(y = 0; y < vt->height; y++) { TextCell *c = vt->cells + y1 * vt->width; for(x = 0; x < vt->width; x++) { draw_char_at (vt, x, y, c->ch, c->attrib, c->width, c->combiningglyph); c += c->width; } if (++y1 == vt->total_height) y1 = 0; } rfbMarkRectAsModified (vt->screen, 0, 0, vt->maxx, vt->maxy); vncterm_show_cursor (vt, 1); } static void vncterm_scroll_down (vncTerm *vt, int top, int bottom, int lines) { if ((top + lines) >= bottom) { lines = bottom - top -1; } if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) { return; } int h = lines * 16; int y0 = top*16; int y1 = y0 + h; int y2 = bottom*16; int rowstride = vt->screen->paddedWidthInBytes; int rows = (bottom - top - lines)*16; char *in = vt->screen->frameBuffer+y0*rowstride; char *out = vt->screen->frameBuffer+y1*rowstride; memmove(out,in, rowstride*rows); memset(vt->screen->frameBuffer+y0*rowstride, 0, h*rowstride); rfbMarkRectAsModified (vt->screen, 0, y0, vt->screen->width, y2); int i; for(i = bottom - top - lines - 1; i >= 0; i--) { int src = ((vt->y_base + top + i) % vt->total_height)*vt->width; int dst = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width; memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof (TextCell)); } for (i = 0; i < lines; i++) { int j; TextCell *c = vt->cells + ((vt->y_base + top + i) % vt->total_height)*vt->width; for(j = 0; j < vt->width; j++) { c->attrib = vt->default_attrib; c->ch = ' '; c->width = 1; c->combiningglyph = 0; c++; } } } static void vncterm_scroll_up (vncTerm *vt, int top, int bottom, int lines, int moveattr) { if ((top + lines) >= bottom) { lines = bottom - top - 1; } if (top < 0 || bottom > vt->height || top >= bottom || lines < 1) { return; } int h = lines * 16; int y0 = top*16; int y1 = (top + lines)*16; int y2 = bottom*16; int rowstride = vt->screen->paddedWidthInBytes; int rows = (bottom - top - lines)*16; char *in = vt->screen->frameBuffer+y1*rowstride; char *out = vt->screen->frameBuffer+y0*rowstride; memmove(out,in, rowstride*rows); memset(vt->screen->frameBuffer+(y2-h)*rowstride, 0, h*rowstride); rfbMarkRectAsModified (vt->screen, 0, y0, vt->screen->width, y2); if (!moveattr) return; // move attributes int i; for(i = 0; i < (bottom - top - lines); i++) { int dst = ((vt->y_base + top + i) % vt->total_height)*vt->width; int src = ((vt->y_base + top + lines + i) % vt->total_height)*vt->width; memmove(vt->cells + dst, vt->cells + src, vt->width*sizeof (TextCell)); } for (i = 1; i <= lines; i++) { int j; TextCell *c = vt->cells + ((vt->y_base + bottom - i) % vt->total_height)*vt->width; for(j = 0; j < vt->width; j++) { c->attrib = vt->default_attrib; c->ch = ' '; c->width = 1; c->combiningglyph = 0; c++; } } } static void vncterm_virtual_scroll (vncTerm *vt, int lines) { if (vt->altbuf || lines == 0) return; if (lines < 0) { lines = -lines; int i = vt->scroll_height; if (i > vt->total_height - vt->height) i = vt->total_height - vt->height; int y1 = vt->y_base - i; if (y1 < 0) y1 += vt->total_height; for(i = 0; i < lines; i++) { if (vt->y_displ == y1) break; if (--vt->y_displ < 0) { vt->y_displ = vt->total_height - 1; } } } else { int i; for(i = 0; i < lines; i++) { if (vt->y_displ == vt->y_base) break; if (++vt->y_displ == vt->total_height) { vt->y_displ = 0; } } } vncterm_refresh (vt); } static void vncterm_respond_esc (vncTerm *vt, const char *esc) { int len = strlen (esc); int i; if (vt->ibuf_count < (IBUFSIZE - 1 - len)) { vt->ibuf[vt->ibuf_count++] = 27; for (i = 0; i < len; i++) { vt->ibuf[vt->ibuf_count++] = esc[i]; } } } static void vncterm_put_lf (vncTerm *vt) { if (vt->cy + 1 == vt->region_bottom) { if (vt->altbuf || vt->region_top != 0 || vt->region_bottom != vt->height) { vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, 1, 1); return; } if (vt->y_displ == vt->y_base) { vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, 1, 0); } if (vt->y_displ == vt->y_base) { if (++vt->y_displ == vt->total_height) { vt->y_displ = 0; } } if (++vt->y_base == vt->total_height) { vt->y_base = 0; } if (vt->scroll_height < vt->total_height) { vt->scroll_height++; } int y1 = (vt->y_base + vt->height - 1) % vt->total_height; TextCell *c = &vt->cells[y1 * vt->width]; int x; for (x = 0; x < vt->width; x++) { c->ch = ' '; c->width = 1; c->combiningglyph = 0; c->attrib = vt->default_attrib; c++; } // fprintf (stderr, "BASE: %d DISPLAY %d\n", vt->y_base, vt->y_displ); } else if (vt->cy < vt->height - 1) { vt->cy += 1; } } static void vncterm_csi_m (vncTerm *vt) { int i; for (i = 0; i < vt->esc_count; i++) { switch (vt->esc_buf[i]) { case 0: /* reset all console attributes to default */ vt->cur_attrib = vt->default_attrib; break; case 1: vt->cur_attrib.bold = 1; break; case 4: vt->cur_attrib.uline = 1; break; case 5: vt->cur_attrib.blink = 1; break; case 7: vt->cur_attrib.invers = 1; break; case 8: vt->cur_attrib.unvisible = 1; break; case 10: vt->cur_enc = LAT1_MAP; // fixme: dispaly controls = 0 ? // fixme: toggle meta = 0 ? break; case 11: vt->cur_enc = IBMPC_MAP; // fixme: dispaly controls = 1 ? // fixme: toggle meta = 0 ? break; case 12: vt->cur_enc = IBMPC_MAP; // fixme: dispaly controls = 1 ? // fixme: toggle meta = 1 ? break; case 22: vt->cur_attrib.bold = 0; break; case 24: vt->cur_attrib.uline = 0; break; case 25: vt->cur_attrib.blink = 0; break; case 27: vt->cur_attrib.invers = 0; break; case 28: vt->cur_attrib.unvisible = 0; break; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: /* set foreground color */ vt->cur_attrib.fgcol = color_table [vt->esc_buf[i] - 30]; break; case 38: /* reset color to default, enable underline */ vt->cur_attrib.fgcol = vt->default_attrib.fgcol; vt->cur_attrib.uline = 1; break; case 39: /* reset color to default, disable underline */ vt->cur_attrib.fgcol = vt->default_attrib.fgcol; vt->cur_attrib.uline = 0; break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: /* set background color */ vt->cur_attrib.bgcol = color_table [vt->esc_buf[i] - 40]; break; case 49: /* reset background color */ vt->cur_attrib.bgcol = vt->default_attrib.bgcol; break; default: fprintf (stderr, "unhandled ESC[%d m code\n",vt->esc_buf[i]); //fixme: implement } } } static void vncterm_save_cursor (vncTerm *vt) { vt->cx_saved = vt->cx; vt->cy_saved = vt->cy; vt->cur_attrib_saved = vt->cur_attrib; vt->charset_saved = vt->charset; vt->g0enc_saved = vt->g0enc; vt->g1enc_saved = vt->g1enc; vt->cur_enc_saved = vt->cur_enc; } static void vncterm_restore_cursor (vncTerm *vt) { vt->cx = vt->cx_saved; vt->cy = vt->cy_saved; vt->cur_attrib = vt->cur_attrib_saved; vt->charset = vt->charset_saved; vt->g0enc = vt->g0enc_saved; vt->g1enc = vt->g1enc_saved; vt->cur_enc = vt->cur_enc_saved; } static void vncterm_set_alternate_buffer (vncTerm *vt, int on_off) { int x, y; vt->y_displ = vt->y_base; if (on_off) { if (vt->altbuf) return; vt->altbuf = 1; /* alternate buffer & cursor */ vncterm_save_cursor (vt); /* save screen to altcels */ for (y = 0; y < vt->height; y++) { int y1 = (vt->y_base + y) % vt->total_height; for (x = 0; x < vt->width; x++) { vt->altcells[y*vt->width + x] = vt->cells[y1*vt->width + x]; } } /* clear screen */ for (y = 0; y <= vt->height; y++) { for (x = 0; x < vt->width; x++) { vncterm_clear_xy (vt, x, y); } } } else { if (vt->altbuf == 0) return; vt->altbuf = 0; /* restore saved data */ for (y = 0; y < vt->height; y++) { int y1 = (vt->y_base + y) % vt->total_height; for (x = 0; x < vt->width; x++) { vt->cells[y1*vt->width + x] = vt->altcells[y*vt->width + x]; } } vncterm_restore_cursor (vt); } vncterm_refresh (vt); } static void vncterm_set_mode (vncTerm *vt, int on_off) { int i; for (i = 0; i <= vt->esc_count; i++) { if (vt->esc_ques) { /* DEC private modes set/reset */ switch(vt->esc_buf[i]) { case 10: /* X11 mouse reporting on/off */ case 1000: vt->report_mouse = on_off; break; case 1049: /* start/end special app mode (smcup/rmcup) */ vncterm_set_alternate_buffer (vt, on_off); break; case 25: /* Cursor on/off */ case 9: /* X10 mouse reporting on/off */ case 6: /* Origin relative/absolute */ case 1: /* Cursor keys in appl mode*/ case 5: /* Inverted screen on/off */ case 7: /* Autowrap on/off */ case 8: /* Autorepeat on/off */ break; } } else { /* ANSI modes set/reset */ /* fixme: implement me */ } } } static void vncterm_gotoxy (vncTerm *vt, int x, int y) { /* verify all boundaries */ if (x < 0) { x = 0; } else if (x >= vt->width) { x = vt->width - 1; } vt->cx = x; if (y < 0) { y = 0; } else if (y >= vt->height) { y = vt->height - 1; } vt->cy = y; } enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey, EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd, ESpalette, ESidquery, ESosc1, ESosc2}; static void vncterm_putchar (vncTerm *vt, unicode ch) { int x, y, i, c; #ifdef DEBUG if (!vt->tty_state) fprintf (stderr, "CHAR:%2d: %4x '%c' (cur_enc %d) %d %d\n", vt->tty_state, ch, ch, vt->cur_enc, vt->cx, vt->cy); #endif switch(vt->tty_state) { case ESesc: vt->tty_state = ESnormal; switch (ch) { case '[': vt->tty_state = ESsquare; break; case ']': vt->tty_state = ESnonstd; break; case '%': vt->tty_state = ESpercent; break; case '7': vncterm_save_cursor (vt); break; case '8': vncterm_restore_cursor (vt); break; case '(': vt->tty_state = ESsetG0; // SET G0 break; case ')': vt->tty_state = ESsetG1; // SET G1 break; case 'M': /* cursor up (ri) */ if (vt->cy == vt->region_top) vncterm_scroll_down (vt, vt->region_top, vt->region_bottom, 1); else if (vt->cy > 0) { vt->cy--; } break; case '>': /* numeric keypad - ignored */ break; case '=': /* appl. keypad - ignored */ break; default: #ifdef DEBUG fprintf(stderr, "got unhandled ESC%c %d\n", ch, ch); #endif break; } break; case ESnonstd: /* Operating System Controls */ vt->tty_state = ESnormal; switch (ch) { case 'P': /* palette escape sequence */ for(i = 0; i < MAX_ESC_PARAMS; i++) { vt->esc_buf[i] = 0; } vt->esc_count = 0; vt->tty_state = ESpalette; break; case 'R': /* reset palette */ // fixme: reset_palette(vc); break; case '0': case '1': case '2': case '4': vt->osc_cmd = ch; vt->osc_textbuf[0] = 0; vt->tty_state = ESosc1; break; default: #ifdef DEBUG fprintf (stderr, "unhandled OSC %c\n", ch); #endif vt->tty_state = ESnormal; break; } break; case ESosc1: vt->tty_state = ESnormal; if (ch == ';') { vt->tty_state = ESosc2; } else { #ifdef DEBUG fprintf (stderr, "got illegal OSC sequence\n"); #endif } break; case ESosc2: if (ch != 0x9c && ch != 7) { int i = 0; while (vt->osc_textbuf[i]) i++; vt->osc_textbuf[i++] = ch; vt->osc_textbuf[i] = 0; } else { #ifdef DEBUG fprintf (stderr, "OSC:%c:%s\n", vt->osc_cmd, vt->osc_textbuf); #endif vt->tty_state = ESnormal; } break; case ESpalette: if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { vt->esc_buf[vt->esc_count++] = (ch > '9' ? (ch & 0xDF) - 'A' + 10 : ch - '0'); if (vt->esc_count == 7) { // fixme: this does not work - please test rfbColourMap *cmap =&vt->screen->colourMap; int i = color_table[vt->esc_buf[0]] * 3, j = 1; cmap->data.bytes[i] = 16 * vt->esc_buf[j++]; cmap->data.bytes[i++] += vt->esc_buf[j++]; cmap->data.bytes[i] = 16 * vt->esc_buf[j++]; cmap->data.bytes[i++] += vt->esc_buf[j++]; cmap->data.bytes[i] = 16 * vt->esc_buf[j++]; cmap->data.bytes[i] += vt->esc_buf[j]; //set_palette(vc); ? vt->tty_state = ESnormal; } } else vt->tty_state = ESnormal; break; case ESsquare: for(i = 0; i < MAX_ESC_PARAMS; i++) { vt->esc_buf[i] = 0; } vt->esc_count = 0; vt->esc_has_par = 0; vt->tty_state = ESgetpars; if (ch == '>') { vt->tty_state = ESidquery; break; } if ((vt->esc_ques = (ch == '?'))) { break; } case ESgetpars: if (ch >= '0' && ch <= '9') { vt->esc_has_par = 1; if (vt->esc_count < MAX_ESC_PARAMS) { vt->esc_buf[vt->esc_count] = vt->esc_buf[vt->esc_count] * 10 + ch - '0'; } break; } else if (ch == ';') { vt->esc_has_par = 1; vt->esc_count++; break; } else { if (vt->esc_has_par) { vt->esc_count++; } vt->tty_state = ESgotpars; } case ESgotpars: vt->tty_state = ESnormal; #ifdef DEBUG char *qes = vt->esc_ques ? "?" : ""; if (vt->esc_count == 0) { fprintf(stderr, "ESC[%s%c\n", qes, ch); } else if (vt->esc_count == 1) { fprintf(stderr, "ESC[%s%d%c\n", qes, vt->esc_buf[0], ch); } else { int i; fprintf(stderr, "ESC[%s%d", qes, vt->esc_buf[0]); for (i = 1; i < vt->esc_count; i++) { fprintf(stderr, ";%d", vt->esc_buf[i]); } fprintf (stderr, "%c\n", ch); } #endif switch (ch) { case 'h': vncterm_set_mode (vt, 1); break; case 'l': vncterm_set_mode (vt, 0); break; case 'm': if (!vt->esc_count) { vt->esc_count++; // default parameter 0 } vncterm_csi_m (vt); break; case 'n': /* report cursor position */ /* TODO: send ESC[row;colR */ break; case 'A': /* move cursor up */ if (vt->esc_buf[0] == 0) { vt->esc_buf[0] = 1; } vncterm_gotoxy (vt, vt->cx, vt->cy - vt->esc_buf[0]); break; case 'B': case 'e': /* move cursor down */ if (vt->esc_buf[0] == 0) { vt->esc_buf[0] = 1; } vncterm_gotoxy (vt, vt->cx, vt->cy + vt->esc_buf[0]); break; case 'C': case 'a': /* move cursor right */ if (vt->esc_buf[0] == 0) { vt->esc_buf[0] = 1; } vncterm_gotoxy (vt, vt->cx + vt->esc_buf[0], vt->cy); break; case 'D': /* move cursor left */ if (vt->esc_buf[0] == 0) { vt->esc_buf[0] = 1; } vncterm_gotoxy (vt, vt->cx - vt->esc_buf[0], vt->cy); break; case 'G': case '`': /* move cursor to column */ vncterm_gotoxy (vt, vt->esc_buf[0] - 1, vt->cy); break; case 'd': /* move cursor to row */ vncterm_gotoxy (vt, vt->cx , vt->esc_buf[0] - 1); break; case 'f': case 'H': /* move cursor to row, column */ vncterm_gotoxy (vt, vt->esc_buf[1] - 1, vt->esc_buf[0] - 1); break; case 'J': switch (vt->esc_buf[0]) { case 0: /* clear to end of screen */ for (y = vt->cy; y < vt->height; y++) { for (x = 0; x < vt->width; x++) { if (y == vt->cy && x < vt->cx) { continue; } vncterm_clear_xy (vt, x, y); } } break; case 1: /* clear from beginning of screen */ for (y = 0; y <= vt->cy; y++) { for (x = 0; x < vt->width; x++) { if (y == vt->cy && x > vt->cx) { break; } vncterm_clear_xy (vt, x, y); } } break; case 2: /* clear entire screen */ for (y = 0; y <= vt->height; y++) { for (x = 0; x < vt->width; x++) { vncterm_clear_xy (vt, x, y); } } break; } break; case 'K': switch (vt->esc_buf[0]) { case 0: /* clear to eol */ for(x = vt->cx; x < vt->width; x++) { vncterm_clear_xy (vt, x, vt->cy); } break; case 1: /* clear from beginning of line */ for (x = 0; x <= vt->cx; x++) { vncterm_clear_xy (vt, x, vt->cy); } break; case 2: /* clear entire line */ for(x = 0; x < vt->width; x++) { vncterm_clear_xy (vt, x, vt->cy); } break; } break; case 'L': /* insert line */ c = vt->esc_buf[0]; if (c > vt->height - vt->cy) c = vt->height - vt->cy; else if (!c) c = 1; vncterm_scroll_down (vt, vt->cy, vt->region_bottom, c); break; case 'M': /* delete line */ c = vt->esc_buf[0]; if (c > vt->height - vt->cy) c = vt->height - vt->cy; else if (!c) c = 1; vncterm_scroll_up (vt, vt->cy, vt->region_bottom, c, 1); break; case 'T': /* scroll down */ c = vt->esc_buf[0]; if (!c) c = 1; vncterm_scroll_down (vt, vt->region_top, vt->region_bottom, c); break; case 'S': /* scroll up */ c = vt->esc_buf[0]; if (!c) c = 1; vncterm_scroll_up (vt, vt->region_top, vt->region_bottom, c, 1); break; case 'P': /* delete c character */ c = vt->esc_buf[0]; if (c > vt->width - vt->cx) c = vt->width - vt->cx; else if (!c) c = 1; for (x = vt->cx; x < vt->width - c; x++) { int y1 = (vt->y_base + vt->cy) % vt->total_height; TextCell *dst = &vt->cells[y1 * vt->width + x]; TextCell *src = dst + c; *dst = *src; vncterm_update_xy (vt, x + c, vt->cy); src->ch = ' '; src->width = 1; src->combiningglyph = 0; src->attrib = vt->default_attrib; vncterm_update_xy (vt, x, vt->cy); } break; case 's': /* save cursor position */ vncterm_save_cursor (vt); break; case 'u': /* restore cursor position */ vncterm_restore_cursor (vt); break; case 'X': /* erase c characters */ c = vt->esc_buf[0]; if (!c) c = 1; if (c > (vt->width - vt->cx)) c = vt->width - vt->cx; for(i = 0; i < c; i++) { vncterm_clear_xy (vt, vt->cx + i, vt->cy); } break; case '@': /* insert c character */ c = vt->esc_buf[0]; if (c > (vt->width - vt->cx)) { c = vt->width - vt->cx; } if (!c) c = 1; for (x = vt->width - c; x >= vt->cx; x--) { int y1 = (vt->y_base + vt->cy) % vt->total_height; TextCell *src = &vt->cells[y1 * vt->width + x]; TextCell *dst = src + c; *dst = *src; vncterm_update_xy (vt, x + c, vt->cy); src->ch = ' '; src->width = 1; src->combiningglyph = 0; src->attrib = vt->cur_attrib; vncterm_update_xy (vt, x, vt->cy); } break; case 'r': /* set region */ if (!vt->esc_buf[0]) vt->esc_buf[0]++; if (!vt->esc_buf[1]) vt->esc_buf[1] = vt->height; /* Minimum allowed region is 2 lines */ if (vt->esc_buf[0] < vt->esc_buf[1] && vt->esc_buf[1] <= vt->height) { vt->region_top = vt->esc_buf[0] - 1; vt->region_bottom = vt->esc_buf[1]; vt->cx = 0; vt->cy = vt->region_top; #ifdef DEBUG fprintf (stderr, "set region %d %d\n", vt->region_top, vt->region_bottom); #endif } break; default: #ifdef DEBUG if (vt->esc_count == 0) { fprintf(stderr, "unhandled escape ESC[%s%c\n", qes, ch); } else if (vt->esc_count == 1) { fprintf(stderr, "unhandled escape ESC[%s%d%c\n", qes, vt->esc_buf[0], ch); } else { int i; fprintf(stderr, "unhandled escape ESC[%s%d", qes, vt->esc_buf[0]); for (i = 1; i < vt->esc_count; i++) { fprintf(stderr, ";%d", vt->esc_buf[i]); } fprintf (stderr, "%c\n", ch); } #endif break; } vt->esc_ques = 0; break; case ESsetG0: // Set G0 vt->tty_state = ESnormal; if (ch == '0') vt->g0enc = GRAF_MAP; else if (ch == 'B') vt->g0enc = LAT1_MAP; else if (ch == 'U') vt->g0enc = IBMPC_MAP; else if (ch == 'K') vt->g0enc = USER_MAP; if (vt->charset == 0) vt->cur_enc = vt->g0enc; break; case ESsetG1: // Set G1 vt->tty_state = ESnormal; if (ch == '0') vt->g1enc = GRAF_MAP; else if (ch == 'B') vt->g1enc = LAT1_MAP; else if (ch == 'U') vt->g1enc = IBMPC_MAP; else if (ch == 'K') vt->g1enc = USER_MAP; if (vt->charset == 1) vt->cur_enc = vt->g1enc; break; case ESidquery: // vt100 query id vt->tty_state = ESnormal; if (ch == 'c') { #ifdef DEBUG fprintf (stderr, "ESC[>c Query term ID\n"); #endif vncterm_respond_esc (vt, TERMIDCODE); } break; case ESpercent: vt->tty_state = ESnormal; switch (ch) { case '@': /* defined in ISO 2022 */ vt->utf8 = 0; break; case 'G': /* prelim official escape code */ case '8': /* retained for compatibility */ vt->utf8 = 1; break; } break; default: // ESnormal vt->tty_state = ESnormal; switch(ch) { case 0: break; case 7: /* alert aka. bell */ rfbSendBell(vt->screen); break; case 8: /* backspace */ if (vt->cx > 0) vt->cx--; break; case 9: /* tabspace */ if (vt->cx + (8 - (vt->cx % 8)) > vt->width) { vt->cx = 0; vncterm_put_lf (vt); } else { vt->cx = vt->cx + (8 - (vt->cx % 8)); } break; case 10: /* LF,*/ case 11: /* VT */ case 12: /* FF */ vncterm_put_lf (vt); break; case 13: /* carriage return */ vt->cx = 0; break; case 14: /* SI (shift in), select character set 1 */ vt->charset = 1; vt->cur_enc = vt->g1enc; /* fixme: display controls = 1 */ break; case 15: /* SO (shift out), select character set 0 */ vt->charset = 0; vt->cur_enc = vt->g0enc; /* fixme: display controls = 0 */ break; case 27: /* esc */ vt->tty_state = ESesc; break; case 127: /* delete */ /* ignore */ break; case 128+27: /* csi */ vt->tty_state = ESsquare; break; default: if (vt->cx >= vt->width) { /* line wrap */ vt->cx = 0; vncterm_put_lf (vt); } int y1 = (vt->y_base + vt->cy) % vt->total_height; int width = wcwidth(ch); if (width > 0) { // normal/wide character TextCell *c = &vt->cells[y1*vt->width + vt->cx]; c->attrib = vt->cur_attrib; c->ch = ch; c->width = width; c->combiningglyph = 0; vncterm_update_xy (vt, vt->cx, vt->cy); vt->cx += width; } else if (width == 0) { // combiningglyph TextCell *c = &vt->cells[y1*vt->width + vt->cx - 1]; c->attrib = vt->cur_attrib; c->combiningglyph = ch; vncterm_update_xy (vt, vt->cx - 1, vt->cy); } else { // non printable character, so we do not save them } break; } break; } } static int vncterm_puts (vncTerm *vt, const char *buf, int len) { unicode tc; vncterm_show_cursor (vt, 0); while (len) { unsigned char c = *buf; len--; buf++; if (vt->tty_state != ESnormal) { // never translate escape sequence tc = c; } else if (vt->utf8 && !vt->cur_enc) { if(c & 0x80) { // utf8 multi-byte sequence if (vt->utf_count > 0 && (c & 0xc0) == 0x80) { // inside UTF8 sequence vt->utf_char = (vt->utf_char << 6) | (c & 0x3f); vt->utf_count--; if (vt->utf_count == 0) { if (vt->utf_char <= USHRT_MAX) { tc = vt->utf_char; } else { tc = 0; } } else { continue; } } else { // first char of a UTF8 sequence if ((c & 0xe0) == 0xc0) { vt->utf_count = 1; vt->utf_char = (c & 0x1f); } else if ((c & 0xf0) == 0xe0) { vt->utf_count = 2; vt->utf_char = (c & 0x0f); } else if ((c & 0xf8) == 0xf0) { vt->utf_count = 3; vt->utf_char = (c & 0x07); } else if ((c & 0xfc) == 0xf8) { vt->utf_count = 4; vt->utf_char = (c & 0x03); } else if ((c & 0xfe) == 0xfc) { vt->utf_count = 5; vt->utf_char = (c & 0x01); } else vt->utf_count = 0; continue; } } else { // utf8 single byte tc = c; vt->utf_count = 0; } } else { // never translate controls if (c >= 32 && c != 127 && c != (128+27)) { tc = translations[vt->cur_enc][c & 0x0ff]; } else { tc = c; } } vncterm_putchar (vt, tc); } vncterm_show_cursor (vt, 1); return len; } void vncterm_kbd_event (rfbBool down, rfbKeySym keySym, rfbClientPtr cl) { vncTerm *vt =(vncTerm *)cl->screen->screenData; static int control = 0; static int shift = 0; char *esc = NULL; //fprintf (stderr, "KEYEVENT:%d: %08x\n", down == 0, keySym);fflush (stderr); if (down) { //fprintf (stderr, "KEYPRESS: %d\n", keySym);fflush (stderr); if (keySym == XK_Shift_L || keySym == XK_Shift_R) { shift = 1; } if (keySym == XK_Control_L || keySym == XK_Control_R) { control = 1; } else if (vt->ibuf_count < (IBUFSIZE - 32)) { if (control) { if(keySym >= 'a' && keySym <= 'z') keySym -= 'a' -1; else if (keySym >= 'A' && keySym <= 'Z') keySym -= 'A'-1; else keySym=0xffff; } else { switch (keySym) { case XK_Escape: keySym=27; break; case XK_Return: keySym='\r'; break; case XK_BackSpace: keySym=8; break; case XK_Tab: keySym='\t'; break; case XK_Delete: /* kdch1 */ case XK_KP_Delete: esc = "[3~";break; case XK_Home: /* khome */ case XK_KP_Home: esc = "OH";break; case XK_End: case XK_KP_End: /* kend */ esc = "OF";break; case XK_Insert: /* kich1 */ case XK_KP_Insert: esc = "[2~";break; case XK_Up: case XK_KP_Up: /* kcuu1 */ esc = "OA";break; case XK_Down: /* kcud1 */ case XK_KP_Down: esc = "OB";break; case XK_Right: case XK_KP_Right: /* kcuf1 */ esc = "OC";break; case XK_Left: case XK_KP_Left: /* kcub1 */ esc = "OD";break; case XK_Page_Up: if (shift) { vncterm_virtual_scroll (vt, -vt->height/2); return; } esc = "[5~";break; case XK_Page_Down: if (shift) { vncterm_virtual_scroll (vt, vt->height/2); return; } esc = "[6~";break; case XK_F1: esc = "OP";break; case XK_F2: esc = "OQ";break; case XK_F3: esc = "OR";break; case XK_F4: esc = "OS";break; case XK_F5: esc = "[15~";break; case XK_F6: esc = "[17~";break; case XK_F7: esc = "[18~";break; case XK_F8: esc = "[19~";break; case XK_F9: esc = "[20~";break; case XK_F10: esc = "[21~";break; case XK_F11: esc = "[23~";break; case XK_F12: esc = "[24~";break; default: break; } } #ifdef DEBUG fprintf (stderr, "KEYPRESS OUT:%s: %d\n", esc, keySym); fflush (stderr); #endif if (vt->y_displ != vt->y_base) { vt->y_displ = vt->y_base; vncterm_refresh (vt); } if (esc) { vncterm_respond_esc (vt, esc); } else if(keySym<0x100) { if (vt->utf8) { int len = ucs2_to_utf8 (keySym & 0x0fff, &vt->ibuf[vt->ibuf_count]); vt->ibuf_count += len; } else { vt->ibuf[vt->ibuf_count++] = (char)keySym; } } } } else { if (keySym == XK_Shift_L || keySym == XK_Shift_R) { shift = 0; } else if (keySym == XK_Control_L || keySym == XK_Control_R) { control = 0; } } } void vncterm_set_xcut_text (char* str, int len, struct _rfbClientRec* cl) { vncTerm *vt =(vncTerm *)cl->screen->screenData; // seems str is Latin-1 encoded if (vt->selection) free (vt->selection); vt->selection = (unicode *)malloc (len*sizeof (unicode)); int i; for (i = 0; i < len; i++) { vt->selection[i] = str[i] & 0xff; } vt->selection_len = len; } static void mouse_report (vncTerm *vt, int butt, int mrx, int mry) { char buf[8]; sprintf (buf, "[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx), (char)('!' + mry)); vncterm_respond_esc (vt, buf); } void vncterm_toggle_marked_cell (vncTerm *vt, int pos) { int x= (pos%vt->width)*8; int y= (pos/vt->width)*16; int i,j; rfbScreenInfoPtr s=vt->screen; char *b = s->frameBuffer+y*s->width+x; for (j=0; j < 16; j++) { for(i=0; i < 8; i++) { b[j*s->width+i] ^= 0x0f; rfbMarkRectAsModified (s, x, y, x+8, y+16); } } } void vncterm_pointer_event (int buttonMask, int x, int y, rfbClientPtr cl) { vncTerm *vt =(vncTerm *)cl->screen->screenData; static int button2_released = 1; static int last_mask = 0; static int sel_start_pos = 0; static int sel_end_pos = 0; int i; int cx = x/8; int cy = y/16; if (cx < 0) cx = 0; if (cx >= vt->width) cx = vt->width - 1; if (cy < 0) cy = 0; if (cy >= vt->height) cy = vt->height - 1; if (vt->report_mouse && buttonMask != last_mask) { last_mask = buttonMask; if (buttonMask & 1) { mouse_report (vt, 0, cx, cy); } if (buttonMask & 2) { mouse_report (vt, 1, cx, cy); } if (buttonMask & 4) { mouse_report (vt, 2, cx, cy); } if (!buttonMask) { mouse_report (vt, 3, cx, cy); } } if (buttonMask & 2) { if(button2_released && vt->selection) { int i; for(i = 0; i < vt->selection_len; i++) { if (vt->ibuf_count < IBUFSIZE - 6) { // uft8 is max 6 characters wide if (vt->utf8) { vt->ibuf_count += ucs2_to_utf8 (vt->selection[i], &vt->ibuf[vt->ibuf_count]); } else { vt->ibuf[vt->ibuf_count++] = vt->selection[i]; } } } if (vt->y_displ != vt->y_base) { vt->y_displ = vt->y_base; vncterm_refresh (vt); } } button2_released = 0; } else { button2_released = 1; } if (buttonMask & 1) { int pos = cy*vt->width + cx; // code borrowed from libvncserver (VNConsole.c) if (!vt->mark_active) { vt->mark_active = 1; sel_start_pos = sel_end_pos = pos; vncterm_toggle_marked_cell (vt, pos); } else { if (pos != sel_end_pos) { if (pos > sel_end_pos) { cx = sel_end_pos; cy=pos; } else { cx=pos; cy=sel_end_pos; } if (cx < sel_start_pos) { if (cy < sel_start_pos) cy--; } else { cx++; } while (cx <= cy) { vncterm_toggle_marked_cell (vt, cx); cx++; } sel_end_pos = pos; } } } else if (vt->mark_active) { vt->mark_active = 0; if (sel_start_pos > sel_end_pos) { int tmp = sel_start_pos - 1; sel_start_pos = sel_end_pos; sel_end_pos = tmp; } int len = sel_end_pos - sel_start_pos + 1; if (vt->selection) free (vt->selection); vt->selection = (unicode *)malloc (len*sizeof (unicode)); vt->selection_len = len; char *sel_latin1 = (char *)malloc (len + 1); for (i = 0; i < len; i++) { int pos = sel_start_pos + i; int x = pos % vt->width; int y1 = ((pos / vt->width) + vt->y_displ) % vt->total_height; TextCell *c = &vt->cells[y1*vt->width + x]; vt->selection[i] = c->ch; sel_latin1[i] = (char)c->ch; c++; } sel_latin1[len] = 0; rfbGotXCutText (vt->screen, sel_latin1, len); free (sel_latin1); while (sel_start_pos <= sel_end_pos) { vncterm_toggle_marked_cell (vt, sel_start_pos++); } } rfbDefaultPtrAddEvent (buttonMask, x, y, cl); } static int client_count = 0; static int client_connected = 0; static int last_client = 1; static time_t last_time = 0; void client_gone (rfbClientPtr client) { client_count--; last_time = time (NULL); if (client_count <= 0) { last_client = 1; } } /* libvncserver callback for when a new client connects */ enum rfbNewClientAction new_client (rfbClientPtr client) { client->clientGoneHook = client_gone; client_count++; last_time = time (NULL); last_client = 0; client_connected = 1; return RFB_CLIENT_ACCEPT; } static char *vncticket = NULL; static void MakeRichCursor(rfbScreenInfoPtr rfbScreen) { int w = 16, h = 16; rfbCursorPtr c = rfbScreen->cursor; char bitmap[] = " " " x " " xx " " xxx " " xxxx " " xxxxx " " xxxxxx " " xxxxxxx " " xxxxxxxx " " xxxxxxxxx " " xxxxxxxxxx " " xxxx " " xxx " " xx " " x " " "; char edge[] = " " " x " " xx " " x x " " x x " " x x " " x x " " x x " " x x " " x x " " x xxxxxx " " x x " " x x " " xx " " x " " "; c = rfbScreen->cursor = rfbMakeXCursor(w,h,bitmap,bitmap); c->richSource = (unsigned char*)calloc(w*h, 1); c->cleanupRichSource = TRUE; for(int j=0;jrichSource[pos] = 15; // white } else { c->richSource[pos] = 0; // black } } } } vncTerm * create_vncterm (int argc, char** argv, int maxx, int maxy) { int i; rfbScreenInfoPtr screen = rfbGetScreen (&argc, argv, maxx, maxy, 8, 1, 1); screen->frameBuffer=(char*)calloc(maxx*maxy, 1); MakeRichCursor(screen); char **passwds = calloc(sizeof(char**), 2); vncTerm *vt = (vncTerm *)calloc (sizeof(vncTerm), 1); rfbColourMap *cmap =&screen->colourMap; cmap->data.bytes = malloc (16*3); for(i=0;i<16;i++) { cmap->data.bytes[i*3 + 0] = default_red[color_table[i]]; cmap->data.bytes[i*3 + 1] = default_grn[color_table[i]]; cmap->data.bytes[i*3 + 2] = default_blu[color_table[i]]; } cmap->count = 16; cmap->is16 = FALSE; screen->serverFormat.trueColour = FALSE; screen->kbdAddEvent = vncterm_kbd_event; screen->setXCutText = vncterm_set_xcut_text; screen->ptrAddEvent = vncterm_pointer_event; screen->desktopName = "VNC Command Terminal"; screen->newClientHook = new_client; vt->maxx = screen->width; vt->maxy = screen->height; vt->width = vt->maxx / 8; vt->height = vt->maxy / 16; vt->total_height = vt->height * 20; vt->scroll_height = 0; vt->y_base = 0; vt->y_displ = 0; vt->region_top = 0; vt->region_bottom = vt->height; vt->g0enc = LAT1_MAP; vt->g1enc = GRAF_MAP; vt->cur_enc = vt->g0enc; vt->charset = 0; /* default text attributes */ vt->default_attrib.bold = 0; vt->default_attrib.uline = 0; vt->default_attrib.blink = 0; vt->default_attrib.invers = 0; vt->default_attrib.unvisible = 0; vt->default_attrib.fgcol = 7; vt->default_attrib.bgcol = 0; vt->cur_attrib = vt->default_attrib; vt->cells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->total_height); for (i = 0; i < vt->width*vt->total_height; i++) { vt->cells[i].ch = ' '; vt->cells[i].attrib = vt->default_attrib; } vt->altcells = (TextCell *)calloc (sizeof (TextCell), vt->width*vt->height); vt->screen = screen; screen->screenData = (void*)vt; //screen->autoPort = 1; if (vncticket) { passwds[0] = vncticket; passwds[1] = NULL; screen->authPasswdData = (void *)passwds; screen->passwordCheck = rfbCheckPasswordByList; } else { rfbRegisterSecurityHandler(&VncSecurityHandlerVencrypt); } rfbInitServer(screen); return vt; } int main (int argc, char** argv) { int i; char **cmdargv = NULL; char *command = "/bin/bash"; // execute normal shell as default int fontfd; struct stat sb; int pid; int master; char ptyname[1024]; fd_set fs, fs1; struct timeval tv, tv1; time_t elapsed, cur_time; struct winsize dimensions; unsigned long width = 0; unsigned long height = 0; if (gnutls_global_init () < 0) { fprintf(stderr, "gnutls_global_init failed\n"); exit(-1); } if (gnutls_dh_params_init (&dh_params) < 0) { fprintf(stderr, "gnutls_dh_params_init failed\n"); exit(-1); } if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) { fprintf(stderr, "gnutls_dh_params_init failed\n"); exit(-1); } for (i = 1; i < argc; i++) { if (!strcmp (argv[i], "-c")) { command = argv[i+1]; cmdargv = &argv[i+1]; argc = i; argv[i] = NULL; break; } } for (i = 1; i < argc; i++) { if (!strcmp (argv[i], "-timeout")) { CHECK_ARGC (argc, argv, i); idle_timeout = atoi(argv[i+1]); rfbPurgeArguments(&argc, &i, 2, argv); i--; } else if (!strcmp (argv[i], "-authpath")) { CHECK_ARGC (argc, argv, i); auth_path = argv[i+1]; rfbPurgeArguments(&argc, &i, 2, argv); i--; } else if (!strcmp (argv[i], "-perm")) { CHECK_ARGC (argc, argv, i); auth_perm = argv[i+1]; rfbPurgeArguments(&argc, &i, 2, argv); i--; } else if (!strcmp (argv[i], "-width")) { CHECK_ARGC (argc, argv, i); errno = 0; width = strtoul(argv[i+1], NULL, 10); if (errno == 0 && width >= 16 && width < 0xFFFF) { screen_width = width; } rfbPurgeArguments(&argc, &i, 2, argv); i--; } else if (!strcmp (argv[i], "-height")) { CHECK_ARGC (argc, argv, i); errno = 0; height = strtoul(argv[i+1], NULL, 10); if (errno == 0 && height >= 32 && height < 0xFFFF) { screen_height = height; } rfbPurgeArguments(&argc, &i, 2, argv); i--; } else if (!strcmp (argv[i], "-notls")) { rfbPurgeArguments(&argc, &i, 1, argv); i--; if ((vncticket = getenv("PVE_VNC_TICKET")) == NULL) { fprintf(stderr, "missing env PVE_VNC_TICKET (-notls)\n"); exit(-1); } } } unsetenv("PVE_VNC_TICKET"); // do not expose this to child #ifdef DEBUG rfbLogEnable (1); gnutls_global_set_log_level(10); gnutls_global_set_log_function(vnc_debug_gnutls_log); #else rfbLogEnable (0); #endif // mmap font file fontfd = open(FONTFILE, O_RDONLY); if (fontfd == -1) { perror("Error opening Fontfile 'FONTFILE'"); exit (-1); } if (fstat(fontfd, &sb) == -1) { perror("Stat on 'FONTFILE' failed"); exit (-1); } fontdata = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fontfd, 0); if (fontdata == MAP_FAILED) { perror("Could not mmap 'FONTFILE'"); exit (-1); } close(fontfd); vncTerm *vt = create_vncterm (argc, argv, screen_width, screen_height); setlocale(LC_ALL, ""); // set from environment char *ctype = setlocale (LC_CTYPE, NULL); // query LC_CTYPE // fixme: ist there a standard way to detect utf8 mode ? if (strcasestr (ctype, ".utf-8")||strcasestr (ctype, ".utf8")) { vt->utf8 = 1; } dimensions.ws_col = vt->width; dimensions.ws_row = vt->height; setenv ("TERM", TERM, 1); pid = forkpty (&master, ptyname, NULL, &dimensions); if(!pid) { // install default signal handlers signal (SIGQUIT, SIG_DFL); signal (SIGTERM, SIG_DFL); signal (SIGINT, SIG_DFL); if (cmdargv) { execvp (command, cmdargv); } else { execlp (command, command, NULL); } perror ("Error: exec failed\n"); exit (-1); // should not be reached } else if (pid == -1) { perror ("Error: fork failed\n"); exit (-1); } FD_ZERO (&fs); FD_SET (master, &fs); tv.tv_sec = 0; tv.tv_usec = 5000; /* 5 ms */ last_time = time (NULL); int count = 0; while (1) { count ++; tv1 = tv; fs1 = fs; cur_time = time (NULL); elapsed = cur_time - last_time; //printf ("Elapsed %ld\n", elapsed); if (last_client) { if (client_connected) { if (idle_timeout && (elapsed >= idle_timeout)) { break; } } else { // wait at least 20 seconds for initial connect if (idle_timeout && (elapsed >= (idle_timeout > 20 ? idle_timeout : 20))) { break; } } } else { // exit after 30 minutes idle time if (elapsed >= 30*60) { break; } } rfbProcessEvents (vt->screen, 40000); /* 40 ms */ if (vt->ibuf_count > 0) { //printf ("DEBUG: WRITE %d %d\n", vt->ibuf[0], vt->ibuf_count); write (master, vt->ibuf, vt->ibuf_count); vt->ibuf_count = 0; last_time = time (NULL); } if (!vt->mark_active) { int num_fds = select (master+1, &fs1, NULL, NULL, &tv1); if (num_fds >= 0) { if (FD_ISSET (master, &fs1)) { char buffer[1024]; int c; while ((c = read (master, buffer, 1024)) == -1) { if (errno != EAGAIN) break; } if (c == -1) break; vncterm_puts (vt, buffer, c); } } else { break; } } } rfbScreenCleanup(vt->screen); kill (pid, 9); int status; waitpid(pid, &status, 0); munmap(fontdata, sb.st_size); exit (0); }