Commit 7084851534c834f00652f90a9da5e4032bd22130
1 parent
e25a5822
VNC password authentication, by Daniel P. Berrange.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3135 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
4 changed files
with
239 additions
and
17 deletions
Makefile.target
| ... | ... | @@ -482,7 +482,7 @@ endif |
| 482 | 482 | ifdef CONFIG_SDL |
| 483 | 483 | VL_OBJS+=sdl.o x_keymap.o |
| 484 | 484 | endif |
| 485 | -VL_OBJS+=vnc.o | |
| 485 | +VL_OBJS+=vnc.o d3des.o | |
| 486 | 486 | ifdef CONFIG_COCOA |
| 487 | 487 | VL_OBJS+=cocoa.o |
| 488 | 488 | COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa -framework IOKit |
| ... | ... | @@ -543,7 +543,7 @@ cocoa.o: cocoa.m |
| 543 | 543 | sdl.o: sdl.c keymaps.c sdl_keysym.h |
| 544 | 544 | $(CC) $(CFLAGS) $(CPPFLAGS) $(SDL_CFLAGS) $(BASE_CFLAGS) -c -o $@ $< |
| 545 | 545 | |
| 546 | -vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h | |
| 546 | +vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h d3des.c d3des.h | |
| 547 | 547 | $(CC) $(CFLAGS) $(CPPFLAGS) $(BASE_CFLAGS) -c -o $@ $< |
| 548 | 548 | |
| 549 | 549 | sdlaudio.o: sdlaudio.c | ... | ... |
monitor.c
| ... | ... | @@ -403,8 +403,17 @@ static void do_change_block(const char *device, const char *filename) |
| 403 | 403 | |
| 404 | 404 | static void do_change_vnc(const char *target) |
| 405 | 405 | { |
| 406 | - if (vnc_display_open(NULL, target) < 0) | |
| 407 | - term_printf("could not start VNC server on %s\n", target); | |
| 406 | + if (strcmp(target, "passwd") == 0 || | |
| 407 | + strcmp(target, "password") == 0) { | |
| 408 | + char password[9]; | |
| 409 | + monitor_readline("Password: ", 1, password, sizeof(password)-1); | |
| 410 | + password[sizeof(password)-1] = '\0'; | |
| 411 | + if (vnc_display_password(NULL, password) < 0) | |
| 412 | + term_printf("could not set VNC server password\n"); | |
| 413 | + } else { | |
| 414 | + if (vnc_display_open(NULL, target) < 0) | |
| 415 | + term_printf("could not start VNC server on %s\n", target); | |
| 416 | + } | |
| 408 | 417 | } |
| 409 | 418 | |
| 410 | 419 | static void do_change(const char *device, const char *target) | ... | ... |
vl.h
| ... | ... | @@ -970,6 +970,7 @@ void cocoa_display_init(DisplayState *ds, int full_screen); |
| 970 | 970 | void vnc_display_init(DisplayState *ds); |
| 971 | 971 | void vnc_display_close(DisplayState *ds); |
| 972 | 972 | int vnc_display_open(DisplayState *ds, const char *display); |
| 973 | +int vnc_display_password(DisplayState *ds, const char *password); | |
| 973 | 974 | void do_info_vnc(void); |
| 974 | 975 | |
| 975 | 976 | /* x_keymap.c */ | ... | ... |
vnc.c
| ... | ... | @@ -30,6 +30,15 @@ |
| 30 | 30 | |
| 31 | 31 | #include "vnc_keysym.h" |
| 32 | 32 | #include "keymaps.c" |
| 33 | +#include "d3des.h" | |
| 34 | + | |
| 35 | +// #define _VNC_DEBUG | |
| 36 | + | |
| 37 | +#ifdef _VNC_DEBUG | |
| 38 | +#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) | |
| 39 | +#else | |
| 40 | +#define VNC_DEBUG(fmt, ...) do { } while (0) | |
| 41 | +#endif | |
| 33 | 42 | |
| 34 | 43 | typedef struct Buffer |
| 35 | 44 | { |
| ... | ... | @@ -54,6 +63,20 @@ typedef void VncSendHextileTile(VncState *vs, |
| 54 | 63 | #define VNC_MAX_HEIGHT 2048 |
| 55 | 64 | #define VNC_DIRTY_WORDS (VNC_MAX_WIDTH / (16 * 32)) |
| 56 | 65 | |
| 66 | +#define VNC_AUTH_CHALLENGE_SIZE 16 | |
| 67 | + | |
| 68 | +enum { | |
| 69 | + VNC_AUTH_INVALID = 0, | |
| 70 | + VNC_AUTH_NONE = 1, | |
| 71 | + VNC_AUTH_VNC = 2, | |
| 72 | + VNC_AUTH_RA2 = 5, | |
| 73 | + VNC_AUTH_RA2NE = 6, | |
| 74 | + VNC_AUTH_TIGHT = 16, | |
| 75 | + VNC_AUTH_ULTRA = 17, | |
| 76 | + VNC_AUTH_TLS = 18, | |
| 77 | + VNC_AUTH_VENCRYPT = 19 | |
| 78 | +}; | |
| 79 | + | |
| 57 | 80 | struct VncState |
| 58 | 81 | { |
| 59 | 82 | QEMUTimer *timer; |
| ... | ... | @@ -73,7 +96,13 @@ struct VncState |
| 73 | 96 | int last_x; |
| 74 | 97 | int last_y; |
| 75 | 98 | |
| 99 | + int major; | |
| 100 | + int minor; | |
| 101 | + | |
| 76 | 102 | char *display; |
| 103 | + char *password; | |
| 104 | + int auth; | |
| 105 | + char challenge[VNC_AUTH_CHALLENGE_SIZE]; | |
| 77 | 106 | |
| 78 | 107 | Buffer output; |
| 79 | 108 | Buffer input; |
| ... | ... | @@ -1125,23 +1154,171 @@ static int protocol_client_init(VncState *vs, char *data, size_t len) |
| 1125 | 1154 | return 0; |
| 1126 | 1155 | } |
| 1127 | 1156 | |
| 1157 | +static void make_challenge(VncState *vs) | |
| 1158 | +{ | |
| 1159 | + int i; | |
| 1160 | + | |
| 1161 | + srand(time(NULL)+getpid()+getpid()*987654+rand()); | |
| 1162 | + | |
| 1163 | + for (i = 0 ; i < sizeof(vs->challenge) ; i++) | |
| 1164 | + vs->challenge[i] = (int) (256.0*rand()/(RAND_MAX+1.0)); | |
| 1165 | +} | |
| 1166 | + | |
| 1167 | +static int protocol_client_auth_vnc(VncState *vs, char *data, size_t len) | |
| 1168 | +{ | |
| 1169 | + char response[VNC_AUTH_CHALLENGE_SIZE]; | |
| 1170 | + int i, j, pwlen; | |
| 1171 | + char key[8]; | |
| 1172 | + | |
| 1173 | + if (!vs->password || !vs->password[0]) { | |
| 1174 | + VNC_DEBUG("No password configured on server"); | |
| 1175 | + vnc_write_u32(vs, 1); /* Reject auth */ | |
| 1176 | + if (vs->minor >= 8) { | |
| 1177 | + static const char err[] = "Authentication failed"; | |
| 1178 | + vnc_write_u32(vs, sizeof(err)); | |
| 1179 | + vnc_write(vs, err, sizeof(err)); | |
| 1180 | + } | |
| 1181 | + vnc_flush(vs); | |
| 1182 | + vnc_client_error(vs); | |
| 1183 | + return 0; | |
| 1184 | + } | |
| 1185 | + | |
| 1186 | + memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); | |
| 1187 | + | |
| 1188 | + /* Calculate the expected challenge response */ | |
| 1189 | + pwlen = strlen(vs->password); | |
| 1190 | + for (i=0; i<sizeof(key); i++) | |
| 1191 | + key[i] = i<pwlen ? vs->password[i] : 0; | |
| 1192 | + deskey(key, EN0); | |
| 1193 | + for (j = 0; j < VNC_AUTH_CHALLENGE_SIZE; j += 8) | |
| 1194 | + des(response+j, response+j); | |
| 1195 | + | |
| 1196 | + /* Compare expected vs actual challenge response */ | |
| 1197 | + if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) { | |
| 1198 | + VNC_DEBUG("Client challenge reponse did not match\n"); | |
| 1199 | + vnc_write_u32(vs, 1); /* Reject auth */ | |
| 1200 | + if (vs->minor >= 8) { | |
| 1201 | + static const char err[] = "Authentication failed"; | |
| 1202 | + vnc_write_u32(vs, sizeof(err)); | |
| 1203 | + vnc_write(vs, err, sizeof(err)); | |
| 1204 | + } | |
| 1205 | + vnc_flush(vs); | |
| 1206 | + vnc_client_error(vs); | |
| 1207 | + } else { | |
| 1208 | + VNC_DEBUG("Accepting VNC challenge response\n"); | |
| 1209 | + vnc_write_u32(vs, 0); /* Accept auth */ | |
| 1210 | + vnc_flush(vs); | |
| 1211 | + | |
| 1212 | + vnc_read_when(vs, protocol_client_init, 1); | |
| 1213 | + } | |
| 1214 | + return 0; | |
| 1215 | +} | |
| 1216 | + | |
| 1217 | +static int start_auth_vnc(VncState *vs) | |
| 1218 | +{ | |
| 1219 | + make_challenge(vs); | |
| 1220 | + /* Send client a 'random' challenge */ | |
| 1221 | + vnc_write(vs, vs->challenge, sizeof(vs->challenge)); | |
| 1222 | + vnc_flush(vs); | |
| 1223 | + | |
| 1224 | + vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge)); | |
| 1225 | + return 0; | |
| 1226 | +} | |
| 1227 | + | |
| 1228 | +static int protocol_client_auth(VncState *vs, char *data, size_t len) | |
| 1229 | +{ | |
| 1230 | + /* We only advertise 1 auth scheme at a time, so client | |
| 1231 | + * must pick the one we sent. Verify this */ | |
| 1232 | + if (data[0] != vs->auth) { /* Reject auth */ | |
| 1233 | + VNC_DEBUG("Reject auth %d\n", (int)data[0]); | |
| 1234 | + vnc_write_u32(vs, 1); | |
| 1235 | + if (vs->minor >= 8) { | |
| 1236 | + static const char err[] = "Authentication failed"; | |
| 1237 | + vnc_write_u32(vs, sizeof(err)); | |
| 1238 | + vnc_write(vs, err, sizeof(err)); | |
| 1239 | + } | |
| 1240 | + vnc_client_error(vs); | |
| 1241 | + } else { /* Accept requested auth */ | |
| 1242 | + VNC_DEBUG("Client requested auth %d\n", (int)data[0]); | |
| 1243 | + switch (vs->auth) { | |
| 1244 | + case VNC_AUTH_NONE: | |
| 1245 | + VNC_DEBUG("Accept auth none\n"); | |
| 1246 | + vnc_write_u32(vs, 0); /* Accept auth completion */ | |
| 1247 | + vnc_read_when(vs, protocol_client_init, 1); | |
| 1248 | + break; | |
| 1249 | + | |
| 1250 | + case VNC_AUTH_VNC: | |
| 1251 | + VNC_DEBUG("Start VNC auth\n"); | |
| 1252 | + return start_auth_vnc(vs); | |
| 1253 | + | |
| 1254 | + default: /* Should not be possible, but just in case */ | |
| 1255 | + VNC_DEBUG("Reject auth %d\n", vs->auth); | |
| 1256 | + vnc_write_u8(vs, 1); | |
| 1257 | + if (vs->minor >= 8) { | |
| 1258 | + static const char err[] = "Authentication failed"; | |
| 1259 | + vnc_write_u32(vs, sizeof(err)); | |
| 1260 | + vnc_write(vs, err, sizeof(err)); | |
| 1261 | + } | |
| 1262 | + vnc_client_error(vs); | |
| 1263 | + } | |
| 1264 | + } | |
| 1265 | + return 0; | |
| 1266 | +} | |
| 1267 | + | |
| 1128 | 1268 | static int protocol_version(VncState *vs, char *version, size_t len) |
| 1129 | 1269 | { |
| 1130 | 1270 | char local[13]; |
| 1131 | - int maj, min; | |
| 1132 | 1271 | |
| 1133 | 1272 | memcpy(local, version, 12); |
| 1134 | 1273 | local[12] = 0; |
| 1135 | 1274 | |
| 1136 | - if (sscanf(local, "RFB %03d.%03d\n", &maj, &min) != 2) { | |
| 1275 | + if (sscanf(local, "RFB %03d.%03d\n", &vs->major, &vs->minor) != 2) { | |
| 1276 | + VNC_DEBUG("Malformed protocol version %s\n", local); | |
| 1137 | 1277 | vnc_client_error(vs); |
| 1138 | 1278 | return 0; |
| 1139 | 1279 | } |
| 1140 | - | |
| 1141 | - vnc_write_u32(vs, 1); /* None */ | |
| 1142 | - vnc_flush(vs); | |
| 1143 | - | |
| 1144 | - vnc_read_when(vs, protocol_client_init, 1); | |
| 1280 | + VNC_DEBUG("Client request protocol version %d.%d\n", vs->major, vs->minor); | |
| 1281 | + if (vs->major != 3 || | |
| 1282 | + (vs->minor != 3 && | |
| 1283 | + vs->minor != 5 && | |
| 1284 | + vs->minor != 7 && | |
| 1285 | + vs->minor != 8)) { | |
| 1286 | + VNC_DEBUG("Unsupported client version\n"); | |
| 1287 | + vnc_write_u32(vs, VNC_AUTH_INVALID); | |
| 1288 | + vnc_flush(vs); | |
| 1289 | + vnc_client_error(vs); | |
| 1290 | + return 0; | |
| 1291 | + } | |
| 1292 | + /* Some broken client report v3.5 which spec requires to be treated | |
| 1293 | + * as equivalent to v3.3 by servers | |
| 1294 | + */ | |
| 1295 | + if (vs->minor == 5) | |
| 1296 | + vs->minor = 3; | |
| 1297 | + | |
| 1298 | + if (vs->minor == 3) { | |
| 1299 | + if (vs->auth == VNC_AUTH_NONE) { | |
| 1300 | + VNC_DEBUG("Tell client auth none\n"); | |
| 1301 | + vnc_write_u32(vs, vs->auth); | |
| 1302 | + vnc_flush(vs); | |
| 1303 | + vnc_read_when(vs, protocol_client_init, 1); | |
| 1304 | + } else if (vs->auth == VNC_AUTH_VNC) { | |
| 1305 | + VNC_DEBUG("Tell client VNC auth\n"); | |
| 1306 | + vnc_write_u32(vs, vs->auth); | |
| 1307 | + vnc_flush(vs); | |
| 1308 | + start_auth_vnc(vs); | |
| 1309 | + } else { | |
| 1310 | + VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->auth); | |
| 1311 | + vnc_write_u32(vs, VNC_AUTH_INVALID); | |
| 1312 | + vnc_flush(vs); | |
| 1313 | + vnc_client_error(vs); | |
| 1314 | + } | |
| 1315 | + } else { | |
| 1316 | + VNC_DEBUG("Telling client we support auth %d\n", vs->auth); | |
| 1317 | + vnc_write_u8(vs, 1); /* num auth */ | |
| 1318 | + vnc_write_u8(vs, vs->auth); | |
| 1319 | + vnc_read_when(vs, protocol_client_auth, 1); | |
| 1320 | + vnc_flush(vs); | |
| 1321 | + } | |
| 1145 | 1322 | |
| 1146 | 1323 | return 0; |
| 1147 | 1324 | } |
| ... | ... | @@ -1156,7 +1333,7 @@ static void vnc_listen_read(void *opaque) |
| 1156 | 1333 | if (vs->csock != -1) { |
| 1157 | 1334 | socket_set_nonblock(vs->csock); |
| 1158 | 1335 | qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque); |
| 1159 | - vnc_write(vs, "RFB 003.003\n", 12); | |
| 1336 | + vnc_write(vs, "RFB 003.008\n", 12); | |
| 1160 | 1337 | vnc_flush(vs); |
| 1161 | 1338 | vnc_read_when(vs, protocol_version, 12); |
| 1162 | 1339 | memset(vs->old_data, 0, vs->ds->linesize * vs->ds->height); |
| ... | ... | @@ -1180,6 +1357,7 @@ void vnc_display_init(DisplayState *ds) |
| 1180 | 1357 | ds->opaque = vs; |
| 1181 | 1358 | vnc_state = vs; |
| 1182 | 1359 | vs->display = NULL; |
| 1360 | + vs->password = NULL; | |
| 1183 | 1361 | |
| 1184 | 1362 | vs->lsock = -1; |
| 1185 | 1363 | vs->csock = -1; |
| ... | ... | @@ -1227,9 +1405,26 @@ void vnc_display_close(DisplayState *ds) |
| 1227 | 1405 | buffer_reset(&vs->output); |
| 1228 | 1406 | vs->need_update = 0; |
| 1229 | 1407 | } |
| 1408 | + vs->auth = VNC_AUTH_INVALID; | |
| 1409 | +} | |
| 1410 | + | |
| 1411 | +int vnc_display_password(DisplayState *ds, const char *password) | |
| 1412 | +{ | |
| 1413 | + VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; | |
| 1414 | + | |
| 1415 | + if (vs->password) { | |
| 1416 | + qemu_free(vs->password); | |
| 1417 | + vs->password = NULL; | |
| 1418 | + } | |
| 1419 | + if (password && password[0]) { | |
| 1420 | + if (!(vs->password = qemu_strdup(password))) | |
| 1421 | + return -1; | |
| 1422 | + } | |
| 1423 | + | |
| 1424 | + return 0; | |
| 1230 | 1425 | } |
| 1231 | 1426 | |
| 1232 | -int vnc_display_open(DisplayState *ds, const char *arg) | |
| 1427 | +int vnc_display_open(DisplayState *ds, const char *display) | |
| 1233 | 1428 | { |
| 1234 | 1429 | struct sockaddr *addr; |
| 1235 | 1430 | struct sockaddr_in iaddr; |
| ... | ... | @@ -1240,15 +1435,32 @@ int vnc_display_open(DisplayState *ds, const char *arg) |
| 1240 | 1435 | socklen_t addrlen; |
| 1241 | 1436 | const char *p; |
| 1242 | 1437 | VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; |
| 1438 | + const char *options; | |
| 1439 | + int password = 0; | |
| 1243 | 1440 | |
| 1244 | 1441 | vnc_display_close(ds); |
| 1245 | - if (strcmp(arg, "none") == 0) | |
| 1442 | + if (strcmp(display, "none") == 0) | |
| 1246 | 1443 | return 0; |
| 1247 | 1444 | |
| 1248 | - if (!(vs->display = strdup(arg))) | |
| 1445 | + if (!(vs->display = strdup(display))) | |
| 1249 | 1446 | return -1; |
| 1447 | + | |
| 1448 | + options = display; | |
| 1449 | + while ((options = strchr(options, ','))) { | |
| 1450 | + options++; | |
| 1451 | + if (strncmp(options, "password", 8) == 0) | |
| 1452 | + password = 1; /* Require password auth */ | |
| 1453 | + } | |
| 1454 | + | |
| 1455 | + if (password) { | |
| 1456 | + VNC_DEBUG("Initializing VNC server with password auth\n"); | |
| 1457 | + vs->auth = VNC_AUTH_VNC; | |
| 1458 | + } else { | |
| 1459 | + VNC_DEBUG("Initializing VNC server with no auth\n"); | |
| 1460 | + vs->auth = VNC_AUTH_NONE; | |
| 1461 | + } | |
| 1250 | 1462 | #ifndef _WIN32 |
| 1251 | - if (strstart(arg, "unix:", &p)) { | |
| 1463 | + if (strstart(display, "unix:", &p)) { | |
| 1252 | 1464 | addr = (struct sockaddr *)&uaddr; |
| 1253 | 1465 | addrlen = sizeof(uaddr); |
| 1254 | 1466 | |
| ... | ... | @@ -1271,7 +1483,7 @@ int vnc_display_open(DisplayState *ds, const char *arg) |
| 1271 | 1483 | addr = (struct sockaddr *)&iaddr; |
| 1272 | 1484 | addrlen = sizeof(iaddr); |
| 1273 | 1485 | |
| 1274 | - if (parse_host_port(&iaddr, arg) < 0) { | |
| 1486 | + if (parse_host_port(&iaddr, display) < 0) { | |
| 1275 | 1487 | fprintf(stderr, "Could not parse VNC address\n"); |
| 1276 | 1488 | free(vs->display); |
| 1277 | 1489 | vs->display = NULL; | ... | ... |