Changes in / [eeb85a8:1ba7e8f]
- Files:
-
- 5 added
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
Makefile
reeb85a8 r1ba7e8f 10 10 11 11 # Program variables 12 objects = account.o bitlbee.o conf.o crypting.o help.o ipc.o irc.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o user.o 13 headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h12 objects = account.o bitlbee.o conf.o crypting.o help.o ipc.o irc.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o user.o dcc.o 13 headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h dcc.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h protocols/ft.h 14 14 subdirs = lib protocols 15 15 -
conf.c
reeb85a8 r1ba7e8f 62 62 conf->ping_timeout = 300; 63 63 conf->user = NULL; 64 conf->max_filetransfer_size = G_MAXUINT; 64 65 proxytype = 0; 65 66 -
conf.h
reeb85a8 r1ba7e8f 50 50 int ping_timeout; 51 51 char *user; 52 size_t max_filetransfer_size; 52 53 } conf_t; 53 54 -
doc/user-guide/commands.xml
reeb85a8 r1ba7e8f 872 872 873 873 </bitlbee-command> 874 875 <bitlbee-command name="transfers"> 876 <short-description>Monitor, cancel, or reject file transfers</short-description> 877 <syntax>transfers [<cancel> id | <reject>]</syntax> 878 879 <description> 880 <para> 881 Without parameters the currently pending file transfers and their status will be listed. Available actions are <emphasis>cancel</emphasis> and <emphasis>reject</emphasis>. See <emphasis>help transfers <action></emphasis> for more information. 882 </para> 883 884 <ircexample> 885 <ircline nick="ulim">transfers</ircline> 886 </ircexample> 887 </description> 888 889 <bitlbee-command name="cancel"> 890 <short-description>Cancels the file transfer with the given id</short-description> 891 <syntax>transfers <cancel> id</syntax> 892 893 <description> 894 <para>Cancels the file transfer with the given id</para> 895 </description> 896 897 <ircexample> 898 <ircline nick="ulim">transfers cancel 1</ircline> 899 <ircline nick="root">Canceling file transfer for test</ircline> 900 </ircexample> 901 </bitlbee-command> 902 903 <bitlbee-command name="reject"> 904 <short-description>Rejects all incoming transfers</short-description> 905 <syntax>transfers <reject></syntax> 906 907 <description> 908 <para>Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it?</para> 909 </description> 910 911 <ircexample> 912 <ircline nick="ulim">transfers reject</ircline> 913 </ircexample> 914 </bitlbee-command> 915 </bitlbee-command> 916 874 917 </chapter> -
irc.c
reeb85a8 r1ba7e8f 28 28 #include "crypting.h" 29 29 #include "ipc.h" 30 #include "dcc.h" 31 32 #include <regex.h> 33 #include <netinet/in.h> 30 34 31 35 static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); … … 995 999 return( 1 ); 996 1000 } 1001 else if( g_strncasecmp( s + 1, "DCC", 3 ) == 0 ) 1002 { 1003 if( u && u->ic && u->ic->acc->prpl->transfer_request ) 1004 { 1005 file_transfer_t *ft = dcc_request( u->ic, s + 5 ); 1006 if ( ft ) 1007 u->ic->acc->prpl->transfer_request( u->ic, ft, u->handle ); 1008 } 1009 return( 1 ); 1010 } 997 1011 else 998 1012 { 999 irc_usermsg( irc, " Non-ACTION CTCP's aren't supported" );1013 irc_usermsg( irc, "Supported CTCPs are ACTION, VERSION, PING, TYPING, DCC" ); 1000 1014 return( 0 ); 1001 1015 } -
irc.h
reeb85a8 r1ba7e8f 84 84 struct query *queries; 85 85 struct account *accounts; 86 GSList *file_transfers; 86 87 87 88 struct __USER *users; -
protocols/jabber/Makefile
reeb85a8 r1ba7e8f 10 10 11 11 # [SH] Program variables 12 objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o 12 objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o si.o s5bytestream.o 13 13 14 14 CFLAGS += -Wall -
protocols/jabber/iq.c
reeb85a8 r1ba7e8f 90 90 pack = 0; 91 91 } 92 else if( strcmp( s, XMLNS_DISCO VER) == 0 )93 { 94 const char *features[] = { XMLNS_DISCO VER,92 else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) 93 { 94 const char *features[] = { XMLNS_DISCO_INFO, 95 95 XMLNS_VERSION, 96 96 XMLNS_TIME, … … 98 98 XMLNS_MUC, 99 99 XMLNS_PING, 100 XMLNS_SI, 101 XMLNS_BYTESTREAMS, 102 XMLNS_FILETRANSFER, 100 103 NULL }; 101 104 const char **f; … … 117 120 { 118 121 xt_free_node( reply ); 119 reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );122 reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); 120 123 pack = 0; 121 124 } … … 123 126 else if( strcmp( type, "set" ) == 0 ) 124 127 { 125 if( !( c = xt_find_node( node->children, "query" ) ) || 128 if( ( c = xt_find_node( node->children, "si" ) ) && 129 ( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) ) 130 { 131 return jabber_si_handle_request( ic, node, c ); 132 } else if( !( c = xt_find_node( node->children, "query" ) ) || 126 133 !( s = xt_find_attr( c, "xmlns" ) ) ) 127 134 { 128 135 imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type ); 129 136 return XT_HANDLED; 130 } 131 137 } else if( strcmp( s, XMLNS_ROSTER ) == 0 ) 138 { 132 139 /* This is a roster push. XMPP servers send this when someone 133 140 was added to (or removed from) the buddy list. AFAIK they're 134 141 sent even if we added this buddy in our own session. */ 135 if( strcmp( s, XMLNS_ROSTER ) == 0 )136 {137 142 int bare_len = strlen( ic->acc->user ); 138 143 … … 151 156 152 157 xt_free_node( reply ); 153 reply = jabber_make_error_packet( node, "not-allowed", "cancel" );158 reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); 154 159 pack = 0; 155 160 } 156 } 157 else 161 } else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) 162 { 163 /* Bytestream Request (stage 2 of file transfer) */ 164 return jabber_bs_recv_request( ic, node, c ); 165 } else 158 166 { 159 167 xt_free_node( reply ); 160 reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );168 reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); 161 169 pack = 0; 162 170 } … … 568 576 return st; 569 577 } 578 579 xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); 580 581 xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) 582 { 583 struct xt_node *node, *query; 584 struct jabber_buddy *bud; 585 586 if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) 587 { 588 /* Who cares about the unknown... */ 589 imcb_log( ic, "Couldnt find the man: %s", bare_jid); 590 return 0; 591 } 592 593 if( bud->features ) /* been here already */ 594 return XT_HANDLED; 595 596 node = xt_new_node( "query", NULL, NULL ); 597 xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); 598 599 if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) 600 { 601 imcb_log( ic, "WARNING: Couldn't generate feature query" ); 602 xt_free_node( node ); 603 } 604 605 jabber_cache_add( ic, query, jabber_iq_parse_features ); 606 607 return jabber_write_packet( ic, query ); 608 } 609 610 xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) 611 { 612 struct xt_node *c; 613 struct jabber_buddy *bud; 614 char *feature; 615 616 if( !( c = xt_find_node( node->children, "query" ) ) || 617 !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) ) 618 { 619 imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); 620 return XT_HANDLED; 621 } 622 if( ( bud = jabber_buddy_by_jid( ic, xt_find_attr( node, "from") , 0 ) ) == NULL ) 623 { 624 /* Who cares about the unknown... */ 625 imcb_log( ic, "Couldnt find the man: %s", xt_find_attr( node, "from")); 626 return 0; 627 } 628 629 c = c->children; 630 while( ( c = xt_find_node( c, "feature" ) ) ) { 631 feature = xt_find_attr( c, "var" ); 632 bud->features = g_slist_append(bud->features, g_strdup(feature) ); 633 c = c->next; 634 } 635 636 return XT_HANDLED; 637 } 638 639 xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); 640 641 xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) 642 { 643 struct xt_node *node, *query; 644 struct jabber_data *jd = ic->proto_data; 645 646 node = xt_new_node( "query", NULL, NULL ); 647 xt_add_attr( node, "xmlns", xmlns ); 648 649 if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) 650 { 651 imcb_log( ic, "WARNING: Couldn't generate server query" ); 652 xt_free_node( node ); 653 } 654 655 jd->have_streamhosts--; 656 jabber_cache_add( ic, query, jabber_iq_parse_server_features ); 657 658 return jabber_write_packet( ic, query ); 659 } 660 661 /* 662 * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info 663 */ 664 xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) 665 { 666 struct xt_node *c; 667 struct jabber_data *jd = ic->proto_data; 668 669 if( !( c = xt_find_node( node->children, "query" ) ) || 670 !xt_find_attr( node, "from" ) ) 671 { 672 imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); 673 return XT_HANDLED; 674 } 675 676 jd->have_streamhosts++; 677 678 if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_ITEMS ) == 0 ) 679 { 680 char *item, *itemjid; 681 682 /* answer from server */ 683 684 c = c->children; 685 while( ( c = xt_find_node( c, "item" ) ) ) 686 { 687 item = xt_find_attr( c, "name" ); 688 itemjid = xt_find_attr( c, "jid" ); 689 690 jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); 691 692 c = c->next; 693 } 694 } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) 695 { 696 char *category, *type; 697 698 /* answer from potential proxy */ 699 700 c = c->children; 701 while( ( c = xt_find_node( c, "identity" ) ) ) 702 { 703 category = xt_find_attr( c, "category" ); 704 type = xt_find_attr( c, "type" ); 705 706 if( type && ( strcmp( type, "bytestreams" ) == 0 ) && 707 category && ( strcmp( category, "proxy" ) == 0 ) ) 708 jabber_iq_query_server( ic, xt_find_attr( node, "from" ), XMLNS_BYTESTREAMS ); 709 710 c = c->next; 711 } 712 } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) 713 { 714 char *host, *jid; 715 int port; 716 717 /* answer from proxy */ 718 719 if( ( c = xt_find_node( c->children, "streamhost" ) ) && 720 ( host = xt_find_attr( c, "host" ) ) && 721 ( port = atoi( xt_find_attr( c, "port" ) ) ) && 722 ( jid = xt_find_attr( c, "jid" ) ) ) 723 { 724 jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); 725 sh->jid = g_strdup( jid ); 726 sh->host = g_strdup( host ); 727 sprintf( sh->port, "%u", port ); 728 729 imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); 730 jd->streamhosts = g_slist_append( jd->streamhosts, sh ); 731 } 732 } 733 734 if( jd->have_streamhosts == 0 ) 735 jd->have_streamhosts++; 736 return XT_HANDLED; 737 } -
protocols/jabber/jabber.c
reeb85a8 r1ba7e8f 529 529 ret->send_typing = jabber_send_typing; 530 530 ret->handle_cmp = g_strcasecmp; 531 ret->transfer_request = jabber_si_transfer_request; 531 532 532 533 register_protocol( ret ); -
protocols/jabber/jabber.h
reeb85a8 r1ba7e8f 59 59 } jabber_buddy_flags_t; 60 60 61 /* Stores a streamhost's(a.k.a. proxy) data */ 62 typedef struct 63 { 64 char *jid; 65 char *host; 66 char port[6]; 67 } jabber_streamhost_t; 68 61 69 typedef enum 62 70 { … … 89 97 GHashTable *node_cache; 90 98 GHashTable *buddies; 99 100 GSList *filetransfers; 101 GSList *streamhosts; 102 int have_streamhosts; 91 103 }; 92 104 … … 118 130 struct jabber_away_state *away_state; 119 131 char *away_message; 132 GSList *features; 120 133 121 134 time_t last_act; … … 131 144 char *my_full_jid; /* Separate copy because of case sensitivity. */ 132 145 struct jabber_buddy *me; 146 }; 147 148 struct jabber_transfer 149 { 150 /* bitlbee's handle for this transfer */ 151 file_transfer_t *ft; 152 153 /* the stream's private handle */ 154 gpointer streamhandle; 155 156 struct im_connection *ic; 157 158 int watch_in; 159 int watch_out; 160 161 char *ini_jid; 162 char *tgt_jid; 163 char *iq_id; 164 char *sid; 165 int accepted; 166 167 size_t bytesread, byteswritten; 168 int fd; 169 struct sockaddr_storage saddr; 133 170 }; 134 171 … … 162 199 163 200 /* Some supported extensions/legacy stuff */ 164 #define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ 165 #define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ 166 #define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ 167 #define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ 168 #define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ 169 #define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ 170 #define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */ 171 #define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 0030 */ 172 #define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ 173 #define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user"/* XEP-0045 */ 174 #define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ 201 #define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ 202 #define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ 203 #define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ 204 #define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ 205 #define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ 206 #define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ 207 #define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ 208 #define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ 209 #define XMLNS_DISCO_INFO "http://jabber.org/protocol/disco#info" /* XEP-0030 */ 210 #define XMLNS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" /* XEP-0030 */ 211 #define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ 212 #define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user" /* XEP-0045 */ 213 #define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ 214 #define XMLNS_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ 215 #define XMLNS_SI "http://jabber.org/protocol/si" /* XEP-0095 */ 216 #define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ 217 #define XMLNS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" /* XEP-0065 */ 218 #define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ 175 219 176 220 /* iq.c */ … … 182 226 int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ); 183 227 int jabber_remove_from_roster( struct im_connection *ic, char *handle ); 228 xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); 229 xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); 230 231 /* si.c */ 232 int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); 233 void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); 234 void jabber_si_free_transfer( file_transfer_t *ft); 235 236 /* s5bytestream.c */ 237 int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); 238 gboolean jabber_bs_send_start( struct jabber_transfer *tf ); 239 gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ); 184 240 185 241 /* message.c */ … … 195 251 char *set_eval_tls( set_t *set, char *value ); 196 252 struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); 197 struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type );253 struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); 198 254 void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); 199 255 struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); -
protocols/jabber/jabber_util.c
reeb85a8 r1ba7e8f 97 97 } 98 98 99 struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type )99 struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) 100 100 { 101 101 struct xt_node *node, *c; … … 109 109 c = xt_new_node( "error", NULL, c ); 110 110 xt_add_attr( c, "type", err_type ); 111 112 /* Add the error code, if present */ 113 if (err_code) 114 xt_add_attr( c, "code", err_code ); 111 115 112 116 /* To make the actual error packet, we copy the original packet and … … 287 291 new = g_new( char, len + 1 ); 288 292 for( i = 0; i < len; i ++ ) 293 { 294 /* don't normalize the resource */ 295 if( orig[i] == '/' ) 296 break; 289 297 new[i] = tolower( orig[i] ); 298 } 299 for( ; i < len; i ++ ) 300 new[i] = orig[i]; 290 301 291 302 new[i] = 0; -
protocols/nogaim.h
reeb85a8 r1ba7e8f 43 43 #include "proxy.h" 44 44 #include "md5.h" 45 #include "ft.h" 45 46 46 47 #define BUF_LEN MSG_LEN … … 228 229 * - Most protocols will just want to set this to g_strcasecmp().*/ 229 230 int (* handle_cmp) (const char *who1, const char *who2); 231 232 /* Incoming transfer request */ 233 void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle ); 230 234 }; 231 235 -
root_commands.c
reeb85a8 r1ba7e8f 969 969 irc_usermsg( irc, "Tried to join chat, not sure if this was successful" ); 970 970 g_free( channel ); 971 } 972 } 973 974 static void cmd_transfers( irc_t *irc, char **cmd ) 975 { 976 GSList *files = irc->file_transfers; 977 enum { LIST, REJECT, CANCEL }; 978 int subcmd = LIST; 979 int fid; 980 981 if( !files ) 982 { 983 irc_usermsg( irc, "No pending transfers" ); 984 return; 985 } 986 987 if( cmd[1] && 988 ( strcmp( cmd[1], "reject" ) == 0 ) ) 989 { 990 subcmd = REJECT; 991 } 992 else if( cmd[1] && 993 ( strcmp( cmd[1], "cancel" ) == 0 ) && 994 cmd[2] && 995 ( fid = atoi( cmd[2] ) ) ) 996 { 997 subcmd = CANCEL; 998 } 999 1000 for( ; files; files = g_slist_next( files ) ) 1001 { 1002 file_transfer_t *file = files->data; 1003 1004 switch( subcmd ) { 1005 case LIST: 1006 if ( file->status == FT_STATUS_LISTENING ) 1007 irc_usermsg( irc, 1008 "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name); 1009 else 1010 { 1011 int kb_per_s = 0; 1012 time_t diff = time( NULL ) - file->started; 1013 if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) ) 1014 kb_per_s = file->bytes_transferred / 1024 / diff; 1015 1016 irc_usermsg( irc, 1017 "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 1018 file->bytes_transferred/1024, file->file_size/1024, kb_per_s); 1019 } 1020 break; 1021 case REJECT: 1022 if( file->status == FT_STATUS_LISTENING ) 1023 { 1024 irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name ); 1025 imcb_file_canceled( file, "Denied by user" ); 1026 } 1027 break; 1028 case CANCEL: 1029 if( file->local_id == fid ) 1030 { 1031 irc_usermsg( irc, "Canceling file transfer for %s", file->file_name ); 1032 imcb_file_canceled( file, "Canceled by user" ); 1033 } 1034 break; 1035 } 971 1036 } 972 1037 } … … 992 1057 { "qlist", 0, cmd_qlist, 0 }, 993 1058 { "join_chat", 2, cmd_join_chat, 0 }, 1059 { "transfers", 0, cmd_transfers, 0 }, 994 1060 { NULL } 995 1061 };
Note: See TracChangeset
for help on using the changeset viewer.