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; | ... | ... |