- Timestamp:
- 2015-03-15T14:41:47Z (10 years ago)
- Children:
- 6e74911
- Parents:
- 3752019
- git-author:
- dequis <dx@…> (09-03-15 08:35:50)
- git-committer:
- dequis <dx@…> (15-03-15 14:41:47)
- Location:
- protocols/msn
- Files:
-
- 1 deleted
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
protocols/msn/Makefile
r3752019 rffa5b70 13 13 14 14 # [SH] Program variables 15 objects = msn.o msn_util.o ns.o s b.o soap.o tables.o15 objects = msn.o msn_util.o ns.o soap.o tables.o 16 16 17 17 LFLAGS += -r -
protocols/msn/msn.c
r3752019 rffa5b70 30 30 int msn_chat_id; 31 31 GSList *msn_connections; 32 GSList *msn_switchboards;33 32 34 33 static char *set_eval_display_name(set_t *set, char *value); … … 48 47 49 48 set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc); 50 set_add(&acc->set, "switchboard_keepalives", "false", set_eval_bool, acc);51 49 52 50 acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | … … 80 78 md->away_state = msn_away_state_list; 81 79 md->domaintree = g_tree_new(msn_domaintree_cmp); 82 md-> ns->fd = -1;80 md->fd = -1; 83 81 84 82 msn_connections = g_slist_prepend(msn_connections, ic); 85 83 86 84 imcb_log(ic, "Connecting"); 87 msn_ns_connect(ic, md->ns,server,85 msn_ns_connect(ic, server, 88 86 set_getint(&ic->acc->set, "port")); 89 87 } … … 96 94 97 95 if (md) { 98 msn_ns_close(md->ns); 99 100 while (md->switchboards) { 101 msn_sb_destroy(md->switchboards->data); 102 } 96 msn_ns_close(md); 103 97 104 98 msn_msgq_purge(ic, &md->msgq); … … 154 148 { 155 149 struct bee_user *bu = bee_user_by_handle(ic->bee, ic, who); 156 struct msn_buddy_data *bd = bu ? bu->data : NULL;157 struct msn_switchboard *sb;158 150 159 151 #ifdef DEBUG 160 152 if (strcmp(who, "raw") == 0) { 161 153 msn_ns_write(ic, -1, "%s\r\n", message); 162 } else 154 return 0; 155 } 163 156 #endif 164 if (bd && bd->flags & MSN_BUDDY_FED) { 165 msn_ns_sendmessage(ic, bu, message); 166 } else if ((sb = msn_sb_by_handle(ic, who))) { 167 return(msn_sb_sendmessage(sb, message)); 168 } else { 169 struct msn_message *m; 170 171 /* Create a message. We have to arrange a usable switchboard, and send the message later. */ 172 m = g_new0(struct msn_message, 1); 173 m->who = g_strdup(who); 174 m->text = g_strdup(message); 175 176 return msn_sb_write_msg(ic, m); 177 } 178 157 158 msn_ns_sendmessage(ic, bu, message); 179 159 return(0); 180 160 } … … 198 178 static void msn_set_away(struct im_connection *ic, char *state, char *message) 199 179 { 200 char *uux;180 //char *uux; 201 181 struct msn_data *md = ic->proto_data; 202 182 … … 258 238 static void msn_chat_msg(struct groupchat *c, char *message, int flags) 259 239 { 260 struct msn_switchboard *sb = msn_sb_by_chat(c); 261 262 if (sb) { 263 msn_sb_sendmessage(sb, message); 264 } 265 /* FIXME: Error handling (although this can't happen unless something's 266 already severely broken) disappeared here! */ 240 /* TODO: groupchats*/ 267 241 } 268 242 269 243 static void msn_chat_invite(struct groupchat *c, char *who, char *message) 270 244 { 271 struct msn_switchboard *sb = msn_sb_by_chat(c); 272 273 if (sb) { 274 msn_sb_write(sb, "CAL %d %s\r\n", ++sb->trId, who); 275 } 245 /* TODO: groupchats*/ 276 246 } 277 247 278 248 static void msn_chat_leave(struct groupchat *c) 279 249 { 280 struct msn_switchboard *sb = msn_sb_by_chat(c); 281 282 if (sb) { 283 msn_sb_write(sb, "OUT\r\n"); 284 } 250 /* TODO: groupchats*/ 285 251 } 286 252 287 253 static struct groupchat *msn_chat_with(struct im_connection *ic, char *who) 288 254 { 289 struct msn_switchboard *sb;255 /* TODO: groupchats*/ 290 256 struct groupchat *c = imcb_chat_new(ic, who); 291 292 if ((sb = msn_sb_by_handle(ic, who))) { 293 debug("Converting existing switchboard to %s to a groupchat", who); 294 return msn_sb_to_chat(sb); 295 } else { 296 struct msn_message *m; 297 298 /* Create a magic message. This is quite hackish, but who cares? :-P */ 299 m = g_new0(struct msn_message, 1); 300 m->who = g_strdup(who); 301 m->text = g_strdup(GROUPCHAT_SWITCHBOARD_MESSAGE); 302 303 msn_sb_write_msg(ic, m); 304 305 return c; 306 } 257 return c; 307 258 } 308 259 … … 324 275 static void msn_add_deny(struct im_connection *ic, char *who) 325 276 { 326 struct msn_switchboard *sb;327 328 277 msn_buddy_list_add(ic, MSN_BUDDY_BL, who, who, NULL); 329 330 /* If there's still a conversation with this person, close it. */331 if ((sb = msn_sb_by_handle(ic, who))) {332 msn_sb_destroy(sb);333 }334 278 } 335 279 -
protocols/msn/msn.h
r3752019 rffa5b70 32 32 #define NUDGE_MESSAGE "\r\r\rSHAKE THAT THING\r\r\r" 33 33 #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r" 34 #define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r"35 34 36 35 #ifdef DEBUG_MSN … … 68 67 #define MSN_CAP2 0x0000 69 68 70 #define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \ 69 #define MSN_MESSAGE_HEADERS \ 70 "Routing: 1.0\r\n" \ 71 "To: 1:%s\r\n" \ 72 "From: 1:%s;epid={%s}\r\n" \ 73 "\r\n" \ 74 "Reliability: 1.0\r\n" \ 75 "\r\n" \ 76 "Messaging: 2.0\r\n" \ 77 "Message-Type: Text\r\n" \ 78 "Content-Length: %zd\r\n" \ 71 79 "Content-Type: text/plain; charset=UTF-8\r\n" \ 72 " User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \73 " X-MMS-IM-Format: FN=MS%20Shell%20Dlg; EF=; CO=0; CS=0; PF=0\r\n" \74 " \r\n"80 "X-MMS-IM-Format: FN=Segoe%%20UI; EF=; CO=0; CS=0; PF=0\r\n" \ 81 "\r\n" \ 82 "%s" 75 83 76 84 #define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \ … … 84 92 "ID: 1\r\n" \ 85 93 "\r\n" 86 87 #define MSN_SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \88 "Content-Type: text/x-ping\r\n" \89 "\r\n\r\n"90 94 91 95 #define PROFILE_URL "http://members.msn.com/" … … 99 103 } msn_flags_t; 100 104 101 struct msn_ handler_data {105 struct msn_data { 102 106 int fd, inpa; 103 107 int rxlen; … … 107 111 char *cmd_text; 108 112 109 /* Either ic or sb */110 gpointer data;111 112 int (*exec_command) (struct msn_handler_data *handler, char **cmd, int count);113 int (*exec_message) (struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int count);114 };115 116 struct msn_data {117 113 struct im_connection *ic; 118 114 119 struct msn_handler_data ns[1];120 115 msn_flags_t flags; 121 116 … … 126 121 127 122 GSList *msgq, *grpq, *soapq; 128 GSList *switchboards;129 int sb_failures;130 time_t first_sb_failure;131 123 132 124 const struct msn_away_state *away_state; … … 139 131 GTree *domaintree; 140 132 int adl_todo; 141 };142 143 struct msn_switchboard {144 struct im_connection *ic;145 146 /* The following two are also in the handler. TODO: Clean up. */147 int fd;148 gint inp;149 struct msn_handler_data *handler;150 gint keepalive;151 152 int trId;153 int ready;154 155 int session;156 char *key;157 158 GSList *msgq;159 char *who;160 struct groupchat *chat;161 133 }; 162 134 … … 205 177 #define STATUS_FATAL 1 206 178 #define STATUS_SB_FATAL 2 207 #define STATUS_SB_IM_SPARE 4 /* Make one-to-one conversation switchboard available again, invite failed. */208 #define STATUS_SB_CHAT_SPARE 8 /* Same, but also for groupchats (not used yet). */209 179 210 180 extern int msn_chat_id; … … 218 188 before doing *anything* else. */ 219 189 extern GSList *msn_connections; 220 extern GSList *msn_switchboards;221 190 222 191 /* ns.c */ 223 192 int msn_ns_write(struct im_connection *ic, int fd, const char *fmt, ...) G_GNUC_PRINTF(3, 4); 224 gboolean msn_ns_connect(struct im_connection *ic, struct msn_handler_data *handler,const char *host, int port);225 void msn_ns_close(struct msn_ handler_data *handler);193 gboolean msn_ns_connect(struct im_connection *ic, const char *host, int port); 194 void msn_ns_close(struct msn_data *handler); 226 195 void msn_auth_got_passport_token(struct im_connection *ic, const char *token, const char *error); 227 196 void msn_auth_got_contact_list(struct im_connection *ic); … … 229 198 int msn_ns_sendmessage(struct im_connection *ic, struct bee_user *bu, const char *text); 230 199 void msn_ns_oim_send_queue(struct im_connection *ic, GSList **msgq); 200 int msn_ns_command(struct msn_data *md, char **cmd, int num_parts); 201 int msn_ns_message(struct msn_data *md, char *msg, int msglen, char **cmd, int num_parts); 231 202 232 203 /* msn_util.c */ … … 236 207 void msn_buddy_ask(bee_user_t *bu); 237 208 char **msn_linesplit(char *line); 238 int msn_handler(struct msn_ handler_data *h);209 int msn_handler(struct msn_data *h); 239 210 void msn_msgq_purge(struct im_connection *ic, GSList **list); 240 211 char *msn_p11_challenge(char *challenge); … … 251 222 const struct msn_status_code *msn_status_by_number(int number); 252 223 253 /* sb.c */254 int msn_sb_write(struct msn_switchboard *sb, const char *fmt, ...) G_GNUC_PRINTF(2, 3);;255 struct msn_switchboard *msn_sb_create(struct im_connection *ic, char *host, int port, char *key, int session);256 struct msn_switchboard *msn_sb_by_handle(struct im_connection *ic, const char *handle);257 struct msn_switchboard *msn_sb_by_chat(struct groupchat *c);258 struct msn_switchboard *msn_sb_spare(struct im_connection *ic);259 int msn_sb_sendmessage(struct msn_switchboard *sb, char *text);260 struct groupchat *msn_sb_to_chat(struct msn_switchboard *sb);261 void msn_sb_destroy(struct msn_switchboard *sb);262 gboolean msn_sb_connected(gpointer data, gint source, b_input_condition cond);263 int msn_sb_write_msg(struct im_connection *ic, struct msn_message *m);264 void msn_sb_start_keepalives(struct msn_switchboard *sb, gboolean initial);265 void msn_sb_stop_keepalives(struct msn_switchboard *sb);266 267 224 #endif //_MSN_H -
protocols/msn/msn_util.c
r3752019 rffa5b70 260 260 1: OK */ 261 261 262 int msn_handler(struct msn_handler_data *h) 263 { 262 int msn_handler(struct msn_data *h) 263 { 264 struct im_connection *ic = h->ic; 264 265 int st; 265 266 … … 293 294 ; 294 295 } 295 st = h->exec_command(h, cmd, count);296 st = msn_ns_command(h, cmd, count); 296 297 g_free(cmd_text); 297 298 … … 334 335 } 335 336 336 st = h->exec_message(h, msg, h->msglen, cmd, count);337 st = msn_ns_message(h, msg, h->msglen, cmd, count); 337 338 g_free(msg); 338 339 g_free(h->cmd_text); -
protocols/msn/ns.c
r3752019 rffa5b70 35 35 static gboolean msn_ns_connected(gpointer data, gint source, b_input_condition cond); 36 36 static gboolean msn_ns_callback(gpointer data, gint source, b_input_condition cond); 37 static int msn_ns_command(struct msn_handler_data *handler, char **cmd, int num_parts);38 static int msn_ns_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts);39 37 40 38 static void msn_ns_send_adl_start(struct im_connection *ic); … … 54 52 55 53 if (fd < 0) { 56 fd = md-> ns->fd;54 fd = md->fd; 57 55 } 58 56 … … 73 71 } 74 72 75 gboolean msn_ns_connect(struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port) 76 { 73 gboolean msn_ns_connect(struct im_connection *ic, const char *host, int port) 74 { 75 struct msn_data *handler = ic->proto_data; 76 77 77 if (handler->fd >= 0) { 78 78 closesocket(handler->fd); 79 79 } 80 80 81 handler->exec_command = msn_ns_command;82 handler->exec_message = msn_ns_message;83 handler->data = ic;84 81 handler->fd = proxy_connect(host, port, msn_ns_connected, handler); 85 82 if (handler->fd < 0) { … … 94 91 static gboolean msn_ns_connected(gpointer data, gint source, b_input_condition cond) 95 92 { 96 struct msn_handler_data *handler = data; 97 struct im_connection *ic = handler->data; 98 struct msn_data *md; 99 100 if (!g_slist_find(msn_connections, ic)) { 101 return FALSE; 102 } 103 104 md = ic->proto_data; 93 struct msn_data *md = data; 94 struct msn_data *handler = md; 95 struct im_connection *ic = md->ic; 105 96 106 97 if (source == -1) { … … 137 128 } 138 129 139 void msn_ns_close(struct msn_ handler_data *handler)130 void msn_ns_close(struct msn_data *handler) 140 131 { 141 132 if (handler->fd >= 0) { … … 155 146 static gboolean msn_ns_callback(gpointer data, gint source, b_input_condition cond) 156 147 { 157 struct msn_ handler_data *handler = data;158 struct im_connection *ic = handler-> data;148 struct msn_data *handler = data; 149 struct im_connection *ic = handler->ic; 159 150 160 151 if (msn_handler(handler) == -1) { /* Don't do this on ret == 0, it's already done then. */ … … 168 159 } 169 160 170 static int msn_ns_command(struct msn_handler_data *handler, char **cmd, int num_parts)171 { 172 struct im_connection *ic = handler-> data;173 struct msn_data *md = ic->proto_data;161 int msn_ns_command(struct msn_data *handler, char **cmd, int num_parts) 162 { 163 struct im_connection *ic = handler->ic; 164 struct msn_data *md = handler; 174 165 175 166 if (num_parts == 0) { … … 209 200 210 201 imcb_log(ic, "Transferring to other server"); 211 return msn_ns_connect(ic, handler, server, port); 212 } else if (num_parts >= 6 && strcmp(cmd[2], "SB") == 0) { 213 struct msn_switchboard *sb; 214 215 server = strchr(cmd[3], ':'); 216 if (!server) { 217 imcb_error(ic, "Syntax error"); 218 imc_logout(ic, TRUE); 219 return(0); 220 } 221 *server = 0; 222 port = atoi(server + 1); 223 server = cmd[3]; 224 225 if (strcmp(cmd[4], "CKI") != 0) { 226 imcb_error(ic, "Unknown authentication method for switchboard"); 227 imc_logout(ic, TRUE); 228 return(0); 229 } 230 231 debug("Connecting to a new switchboard with key %s", cmd[5]); 232 233 if ((sb = msn_sb_create(ic, server, port, cmd[5], MSN_SB_NEW)) == NULL) { 234 /* Although this isn't strictly fatal for the NS connection, it's 235 definitely something serious (we ran out of file descriptors?). */ 236 imcb_error(ic, "Could not create new switchboard"); 237 imc_logout(ic, TRUE); 238 return(0); 239 } 240 241 if (md->msgq) { 242 struct msn_message *m = md->msgq->data; 243 GSList *l; 244 245 sb->who = g_strdup(m->who); 246 247 /* Move all the messages to the first user in the message 248 queue to the switchboard message queue. */ 249 l = md->msgq; 250 while (l) { 251 m = l->data; 252 l = l->next; 253 if (strcmp(m->who, sb->who) == 0) { 254 sb->msgq = g_slist_append(sb->msgq, m); 255 md->msgq = g_slist_remove(md->msgq, m); 256 } 257 } 258 } 202 return msn_ns_connect(ic, server, port); 259 203 } else { 260 204 imcb_error(ic, "Syntax error"); … … 361 305 st->name, NULL); 362 306 363 msn_sb_stop_keepalives(msn_sb_by_handle(ic, handle));364 307 } else if (strcmp(cmd[0], "FLN") == 0) { 365 308 const char *handle; … … 371 314 handle = msn_normalize_handle(cmd[1]); 372 315 imcb_buddy_status(ic, handle, 0, NULL, NULL); 373 msn_sb_start_keepalives(msn_sb_by_handle(ic, handle), TRUE);374 } else if (strcmp(cmd[0], "RNG") == 0) {375 struct msn_switchboard *sb;376 char *server;377 int session, port;378 379 if (num_parts < 7) {380 imcb_error(ic, "Syntax error");381 imc_logout(ic, TRUE);382 return(0);383 }384 385 session = atoi(cmd[1]);386 387 server = strchr(cmd[2], ':');388 if (!server) {389 imcb_error(ic, "Syntax error");390 imc_logout(ic, TRUE);391 return(0);392 }393 *server = 0;394 port = atoi(server + 1);395 server = cmd[2];396 397 if (strcmp(cmd[3], "CKI") != 0) {398 imcb_error(ic, "Unknown authentication method for switchboard");399 imc_logout(ic, TRUE);400 return(0);401 }402 403 debug("Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4]);404 405 if ((sb = msn_sb_create(ic, server, port, cmd[4], session)) == NULL) {406 /* Although this isn't strictly fatal for the NS connection, it's407 definitely something serious (we ran out of file descriptors?). */408 imcb_error(ic, "Could not create new switchboard");409 imc_logout(ic, TRUE);410 return(0);411 } else {412 sb->who = g_strdup(msn_normalize_handle(cmd[5]));413 }414 316 } else if (strcmp(cmd[0], "OUT") == 0) { 415 317 int allow_reconnect = TRUE; … … 494 396 handler->msglen = atoi(cmd[1]); 495 397 } 496 } else if ( strcmp(cmd[0], "NFY") == 0) {398 } else if ((strcmp(cmd[0], "NFY") == 0) || (strcmp(cmd[0], "SDG") == 0)) { 497 399 if (num_parts >= 3) { 498 400 handler->msglen = atoi(cmd[2]); … … 526 428 } 527 429 528 static int msn_ns_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts)529 { 530 struct im_connection *ic = handler-> data;430 int msn_ns_message(struct msn_data *handler, char *msg, int msglen, char **cmd, int num_parts) 431 { 432 struct im_connection *ic = handler->ic; 531 433 char *body; 532 434 int blen = 0; … … 717 619 } 718 620 } 719 } else if (strcmp(cmd[0], "UBM") == 0) { 720 /* This one will give us msgs from federated networks. Technically 721 it should also get us offline messages, but I don't know how 722 I can signal MSN servers to use it. */ 723 char *ct, *handle; 724 725 if (strcmp(cmd[1], ic->acc->user) == 0) { 726 /* With MPOP, you'll get copies of your own msgs from other 727 sessions. Discard those at least for now. */ 728 return 1; 729 } 730 731 ct = get_rfc822_header(msg, "Content-Type", msglen); 732 if (strncmp(ct, "text/plain", 10) != 0) { 733 /* Typing notification or something? */ 734 g_free(ct); 735 return 1; 736 } 737 if (strcmp(cmd[2], "1") != 0) { 738 handle = g_strdup_printf("%s:%s", cmd[2], cmd[1]); 739 } else { 740 handle = g_strdup(cmd[1]); 741 } 742 743 imcb_buddy_msg(ic, handle, body, 0, 0); 744 g_free(handle); 621 } else if (strcmp(cmd[0], "SDG") == 0) { 622 char **parts = g_strsplit(msg, "\r\n\r\n", 4); 623 char *from = NULL; 624 char *mt = NULL; 625 char *who = NULL; 626 char *s = NULL; 627 628 if ((from = get_rfc822_header(parts[0], "From", 0)) && 629 (mt = get_rfc822_header(parts[2], "Message-Type", 0)) && 630 (s = strchr(from, ';'))) { 631 632 who = g_strndup(from + 2, s - from - 2); 633 634 if (strcmp(mt, "Control/Typing") == 0) { 635 imcb_buddy_typing(ic, who, OPT_TYPING); 636 } else if (strcmp(mt, "Text") == 0) { 637 imcb_buddy_msg(ic, who, parts[3], 0, 0); 638 } 639 } 640 g_free(from); 641 g_free(mt); 642 g_free(who); 643 return 1; 745 644 } 746 645 … … 893 792 } 894 793 794 // TODO: typing notifications, nudges lol, etc 895 795 int msn_ns_sendmessage(struct im_connection *ic, bee_user_t *bu, const char *text) 896 796 { 897 797 struct msn_data *md = ic->proto_data; 898 int type= 0;899 char *buf , *handle;798 int retval = 0; 799 char *buf; 900 800 901 801 if (strncmp(text, "\r\r\r", 3) == 0) { … … 905 805 } 906 806 907 /* This might be a federated contact. Get its network number, 908 prefixed to bu->handle with a colon. Default is 1. */ 909 for (handle = bu->handle; g_ascii_isdigit(*handle); handle++) { 910 type = type * 10 + *handle - '0'; 911 } 912 if (*handle == ':') { 913 handle++; 914 } else { 915 type = 1; 916 } 917 918 buf = g_strdup_printf("%s%s", MSN_MESSAGE_HEADERS, text); 919 920 if (msn_ns_write(ic, -1, "UUM %d %s %d %d %zd\r\n%s", 921 ++md->trId, handle, type, 922 1, /* type == IM (not nudge/typing) */ 923 strlen(buf), buf)) { 924 return 1; 925 } else { 926 return 0; 927 } 807 buf = g_strdup_printf(MSN_MESSAGE_HEADERS, bu->handle, ic->acc->user, md->uuid, strlen(text), text); 808 retval = msn_ns_write(ic, -1, "SDG %d %zd\r\n%s", ++md->trId, strlen(buf), buf); 809 g_free(buf); 810 return retval; 928 811 } 929 812 -
protocols/msn/tables.c
r3752019 rffa5b70 73 73 { 206, "Domain name missing", 0 }, 74 74 { 207, "Already logged in", 0 }, 75 { 208, "Invalid handle", STATUS_SB_IM_SPARE},75 { 208, "Invalid handle", 0 }, 76 76 { 209, "Forbidden nickname", 0 }, 77 77 { 210, "Buddy list too long", 0 }, 78 78 { 215, "Handle is already in list", 0 }, 79 { 216, "Handle is not in list", STATUS_SB_IM_SPARE},80 { 217, "Person is off-line or non-existent", STATUS_SB_IM_SPARE},79 { 216, "Handle is not in list", 0 }, 80 { 217, "Person is off-line or non-existent", 0 }, 81 81 { 218, "Already in that mode", 0 }, 82 82 { 219, "Handle is already in opposite list", 0 }, … … 113 113 { 711, "Write is blocking", STATUS_FATAL }, 114 114 { 712, "Session is overloaded", STATUS_FATAL }, 115 { 713, "Calling too rapidly", STATUS_SB_IM_SPARE},115 { 713, "Calling too rapidly", 0 }, 116 116 { 714, "Too many sessions", STATUS_FATAL }, 117 117 { 715, "Not expected/Invalid argument/action", 0 },
Note: See TracChangeset
for help on using the changeset viewer.