首页 | 数据结构 | 文件列表 | 数据字段 | 全局定义

login_logout.c

浏览该文件的文档。
00001 
00023 // START OF FILE
00024 /*****************************************************************************/
00025 #include "debug.h"              // gaim_debug
00026 #include "internal.h"           // memcpy, _("get_text")
00027 #include "server.h"             // serv_finish_login
00028 
00029 #include "utils.h"              // gen_ip_str
00030 #include "packet_parse.h"       // create_packet
00031 #include "buddy_info.h"         // qq_send_packet_get_info
00032 #include "buddy_list.h"         // qq_send_packet_get_buddies_list
00033 #include "buddy_status.h"       // QQ_SELF_STATUS_AVAILABLE
00034 #include "char_conv.h"          // qq_to_utf8
00035 #include "crypt.h"              // qq_crypt
00036 #include "group.h"              // qq_group_init
00037 #include "header_info.h"        // QQ_CMD_LOGIN
00038 #include "login_logout.h"
00039 #include "qq_proxy.h"           // qq_connect
00040 #include "send_core.h"          // qq_send_cmd
00041 #include "qq.h"                 // qq_data
00042 
00043 #define QQ_LOGIN_DATA_LENGTH                416 //69    //length of plain login packet
00044 #define QQ_LOGIN_REPLY_OK_PACKET_LEN        139
00045 #define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN  11
00046 
00047 #define QQ_LOGIN_REPLY_OK                   0x00
00048 #define QQ_LOGIN_REPLY_REDIRECT             0x01
00049 #define QQ_LOGIN_REPLY_PWD_ERROR            0x02
00050 #define QQ_LOGIN_REPLY_MISC_ERROR           0xff        // defined by myself
00051 
00052 // for QQ 2003iii 0117, fixed value
00053 /* static const guint8 login_23_51[29] = {
00054         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00055         0x00, 0x00, 0x00, 0x00, 0xbf, 0x14, 0x11, 0x20,
00056         0x03, 0x9d, 0xb2, 0xe6, 0xb3, 0x11, 0xb7, 0x13,
00057         0x95, 0x67, 0xda, 0x2c, 0x01 
00058 }; */
00059 
00060 // for QQ 2003iii 0304, fixed value
00061 /*static const guint8 login_23_51[29] = {
00062         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00063         0x00, 0x00, 0x00, 0x00, 0x9a, 0x93, 0xfe, 0x85,
00064         0xd3, 0xd9, 0x2a, 0x41, 0xc8, 0x0d, 0xff, 0xb6,
00065         0x40, 0xb8, 0xac, 0x32, 0x01
00066 };*/
00067 // for QQ 2006 with sp1 modify by Yuan Qingyun
00068 static const guint8 login_23_51[29] = {
00069         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00070         0x00, 0x00, 0x00, 0x00, 0x29, 0xc0, 0xf8, 0xc4,
00071         0x04, 0x3b, 0xee, 0x57, 0x92, 0xd2, 0x42, 0xa6,
00072         0xbe, 0x41, 0x98, 0x97, 0x9e
00073 };
00074 
00075 // fixed value, not affected by version, or mac address
00076 /*static const guint8 login_53_68[16] = {
00077         0x82, 0x2a, 0x91, 0xfd, 0xa5, 0xca, 0x67, 0x4c,
00078         0xac, 0x81, 0x1f, 0x6f, 0x52, 0x05, 0xa7, 0xbf
00079 };*/
00080 // for QQ 2006 with sp1 modify by Yuan Qingyun
00081 static const guint8 login_53_68[16] = {
00082         0x2e, 0xda, 0x0c, 0x59, 0xa7, 0x1a, 0xd6, 0x4a,
00083         0xb1, 0x48, 0x5d, 0xba, 0x37, 0x1e, 0xac, 0xb9
00084 };
00085 // for QQ 2006 with sp1 add by Yuan Qingyun
00086 static const guint8 login_94_118[25] = {
00087         0x01, 0x40, 0x01, 0xd7, 0x50, 0x72, 0xc8, 0x00,
00088         0x10, 0x4a, 0xc3, 0x1b, 0x6c, 0xf9, 0x85, 0xf5,
00089         0xd9, 0xa8, 0x05, 0xac, 0x95, 0xa0, 0xe2, 0x44,
00090         0x01
00091 };
00092 
00093 typedef struct _qq_login_reply_ok qq_login_reply_ok_packet;
00094 typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet;
00095 
00096 struct _qq_login_reply_ok {
00097         guint8 result;
00098         guint8 *session_key;
00099         guint32 uid;
00100         guint8 client_ip[4];    // those detected by server
00101         guint16 client_port;
00102         guint8 server_ip[4];
00103         guint16 server_port;
00104         time_t login_time;
00105         guint8 unknown1[26];
00106         guint8 unknown_server1_ip[4];
00107         guint16 unknown_server1_port;
00108         guint8 unknown_server2_ip[4];
00109         guint16 unknown_server2_port;
00110         guint16 unknown2;       // 0x0001
00111         guint16 unknown3;       // 0x0000
00112         guint8 unknown4[32];
00113         guint8 unknown5[12];
00114         guint8 last_client_ip[4];
00115         time_t last_login_time;
00116         guint8 unknown6[8];
00117 };
00118 
00119 struct _qq_login_reply_redirect {
00120         guint8 result;
00121         guint32 uid;
00122         guint8 new_server_ip[4];
00123         guint16 new_server_port;
00124 };
00125 
00126 extern gint                     // defined in send_core.c
00127  _create_packet_head_seq(guint8 * buf,
00128                          guint8 ** cursor, GaimConnection * gc, guint16 cmd, gboolean is_auto_seq, guint16 * seq);
00129 extern gint                     // defined in send_core.c
00130  _qq_send_packet(GaimConnection * gc, guint8 * buf, gint len, guint16 cmd);
00131 
00132 /*****************************************************************************/
00133 // It is fixed to 16 bytes 0x01 for QQ2003, 
00134 // Any value works (or a random 16 bytes string)
00135 static gchar *_gen_login_key(void)
00136 {
00137         return g_strnfill(QQ_KEY_LENGTH, 0x01);
00138 }                               // _gen_login_key
00139 
00140 /*****************************************************************************/
00141 // process login reply which says OK
00142 static gint _qq_process_login_ok(GaimConnection * gc, guint8 * data, gint len)
00143 {
00144         gint bytes;
00145         guint8 *cursor;
00146         qq_data *qd;
00147         qq_login_reply_ok_packet lrop;
00148 
00149         g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR);
00150 
00151         qd = (qq_data *) gc->proto_data;
00152         cursor = data;
00153         bytes = 0;
00154 
00155         // 000-000: reply code
00156         bytes += read_packet_b(data, &cursor, len, &lrop.result);
00157         // 001-016: session key
00158         lrop.session_key = g_memdup(cursor, QQ_KEY_LENGTH);
00159         cursor += QQ_KEY_LENGTH;
00160         bytes += QQ_KEY_LENGTH;
00161         gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get session_key done\n");
00162         // 017-020: login uid
00163         bytes += read_packet_dw(data, &cursor, len, &lrop.uid);
00164         // 021-024: server detected user public IP
00165         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.client_ip, 4);
00166         // 025-026: server detected user port
00167         bytes += read_packet_w(data, &cursor, len, &lrop.client_port);
00168         // 027-030: server detected itself ip 127.0.0.1 ?
00169         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.server_ip, 4);
00170         // 031-032: server listening port
00171         bytes += read_packet_w(data, &cursor, len, &lrop.server_port);
00172         // 033-036: login time for current session
00173         bytes += read_packet_dw(data, &cursor, len, (guint32 *) & lrop.login_time);
00174         // 037-062: 26 bytes, unknown
00175         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown1, 26);
00176         // 063-066: unknown server1 ip address
00177         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown_server1_ip, 4);
00178         // 067-068: unknown server1 port
00179         bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server1_port);
00180         // 069-072: unknown server2 ip address
00181         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown_server2_ip, 4);
00182         // 073-074: unknown server2 port
00183         bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server2_port);
00184         // 075-076: 2 bytes unknown
00185         bytes += read_packet_w(data, &cursor, len, &lrop.unknown2);
00186         // 077-078: 2 bytes unknown
00187         bytes += read_packet_w(data, &cursor, len, &lrop.unknown3);
00188         // 079-110: 32 bytes unknown 
00189         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown4, 32);
00190         // 111-122: 12 bytes unknown
00191         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown5, 12);
00192         // 123-126: login IP of last session
00193         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.last_client_ip, 4);
00194         // 127-130: login time of last session
00195         bytes += read_packet_dw(data, &cursor, len, (guint32 *) & lrop.last_login_time);
00196         // 131-138: 8 bytes unknown 
00197         bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown6, 8);
00198 
00199         if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {    // fail parsing login info       
00200                 gaim_debug(GAIM_DEBUG_WARNING, "QQ",
00201                            "Fail parsing login info, expect %d bytes, read %d bytes\n",
00202                            QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
00203         }                       // but we still goes on as login OK
00204 
00205         qd->session_key = g_memdup(lrop.session_key, QQ_KEY_LENGTH);
00206         qd->my_ip = gen_ip_str(lrop.client_ip);
00207         qd->my_port = lrop.client_port;
00208         qd->login_time = lrop.login_time;
00209         qd->last_login_time = lrop.last_login_time;
00210         qd->last_login_ip = gen_ip_str(lrop.last_client_ip);
00211 
00212         g_free(lrop.session_key);
00213 
00214         gaim_connection_set_state(gc, GAIM_CONNECTED);
00215         serv_finish_login(gc);
00216         qd->logged_in = TRUE;   // must be defined after sev_finish_login
00217 
00218         // now initiate QQ Qun, do it first as it may take longer to finish
00219         qq_group_init(gc);
00220 
00221         // Now goes on updating my icon/nickname, not showing info_window
00222         qq_send_packet_get_info(gc, qd->uid, FALSE);
00223         // change my status manually, even server may broadcast my online
00224         qd->status = (qd->login_mode == QQ_LOGIN_MODE_HIDDEN) ? QQ_SELF_STATUS_INVISIBLE : QQ_SELF_STATUS_AVAILABLE;
00225         qq_send_packet_change_status(gc);
00226         // now refresh buddy list
00227         qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START);
00228 
00229         return QQ_LOGIN_REPLY_OK;
00230 }                               // _qq_process_login_ok
00231 
00232 /*****************************************************************************/
00233 // process login reply packet which includes redirected new server address
00234 static gint _qq_process_login_redirect(GaimConnection * gc, guint8 * data, gint len)
00235 {
00236         gint bytes, ret;
00237         guint8 *cursor;
00238         gchar *new_server_str;
00239         qq_data *qd;
00240         qq_login_reply_redirect_packet lrrp;
00241 
00242         g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR);
00243 
00244         qd = (qq_data *) gc->proto_data;
00245         cursor = data;
00246         bytes = 0;
00247         // 000-000: reply code
00248         bytes += read_packet_b(data, &cursor, len, &lrrp.result);
00249         // 001-004: login uid
00250         bytes += read_packet_dw(data, &cursor, len, &lrrp.uid);
00251         // 005-008: redirected new server IP
00252         bytes += read_packet_data(data, &cursor, len, lrrp.new_server_ip, 4);
00253         // 009-010: redirected new server port
00254         bytes += read_packet_w(data, &cursor, len, &lrrp.new_server_port);
00255 
00256         if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
00257                 gaim_debug(GAIM_DEBUG_ERROR, "QQ",
00258                            "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
00259                            QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
00260                 ret = QQ_LOGIN_REPLY_MISC_ERROR;
00261         } else {                // start new connection
00262                 new_server_str = gen_ip_str(lrrp.new_server_ip);
00263                 gaim_debug(GAIM_DEBUG_WARNING, "QQ",
00264                            "Redirected to new server: %s:%d\n", new_server_str, lrrp.new_server_port);
00265                 qq_connect(gc->account, new_server_str, lrrp.new_server_port, qd->use_tcp, TRUE);
00266                 g_free(new_server_str);
00267                 ret = QQ_LOGIN_REPLY_REDIRECT;
00268         }                       // if bytes != QQ_LOGIN_REPLY_MISC_ERROR 
00269 
00270         return ret;
00271 }                               // _qq_process_login_redirect
00272 
00273 /*****************************************************************************/
00274 // process login reply which says wrong password
00275 static gint _qq_process_login_wrong_pwd(GaimConnection * gc, guint8 * data, gint len)
00276 {
00277         gchar *server_reply, *server_reply_utf8;
00278         server_reply = g_new0(gchar, len);
00279         g_memmove(server_reply, data + 1, len - 1);
00280         server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT);
00281         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Wrong password, server msg in UTF8: %s\n", server_reply_utf8);
00282         g_free(server_reply);
00283         g_free(server_reply_utf8);
00284 
00285         return QQ_LOGIN_REPLY_PWD_ERROR;
00286 }                               // _qq_process_login_wrong_pwd
00287 
00288 /*****************************************************************************/
00289 // send login packet to QQ server
00290 void qq_send_packet_login(GaimConnection * gc)
00291 {
00292         qq_data *qd;
00293         guint8 *buf, *cursor, *raw_data, *encrypted_data;
00294         guint16 seq_ret;
00295         gint encrypted_len, bytes;
00296 
00297         g_return_if_fail(gc != NULL && gc->proto_data != NULL);
00298 
00299         qd = (qq_data *) gc->proto_data;
00300         buf = g_newa(guint8, MAX_PACKET_SIZE);
00301         raw_data = g_new0(guint8, QQ_LOGIN_DATA_LENGTH);
00302         encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16);     // 16 bytes more
00303         qd->inikey = _gen_login_key();
00304 
00305         // now generate the encrypted data
00306         // 000-015 use pwkey as key to encrypt empty string
00307         qq_crypt(ENCRYPT, "", 0, qd->pwkey, raw_data, &encrypted_len);
00308         // 016-016 
00309         raw_data[16] = 0x00;
00310         // 017-020, used to be IP, now zero
00311         *((guint32 *) (raw_data + 17)) = 0x00000000;
00312         // 021-022, used to be port, now zero
00313         *((guint16 *) (raw_data + 21)) = 0x0000;
00314         // 023-051, fixed value, unknown
00315         g_memmove(raw_data + 23, login_23_51, 29);
00316         // 052-052, login mode
00317         raw_data[52] = qd->login_mode;
00318         // 053-068, fixed value, maybe related to per machine
00319         g_memmove(raw_data + 53, login_53_68, 16);
00320         // 069-069, login token length
00321         raw_data[69] = qd->token_len;
00322         // 070-093, login token
00323         g_memmove(raw_data + 70, qd->ptoken, qd->token_len);
00324         // 094-118, unknown
00325         g_memmove(raw_data + 70 + qd->token_len, login_94_118, 25);
00326         qq_crypt(ENCRYPT, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey, encrypted_data, &encrypted_len);
00327 
00328         cursor = buf;
00329         bytes = 0;
00330         bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_LOGIN, TRUE, &seq_ret);
00331         bytes += create_packet_dw(buf, &cursor, qd->uid);
00332         bytes += create_packet_data(buf, &cursor, qd->inikey, QQ_KEY_LENGTH);
00333         bytes += create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
00334         bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
00335 
00336         if (bytes == (cursor - buf))    // packet creation OK
00337                 _qq_send_packet(gc, buf, bytes, QQ_CMD_LOGIN);
00338         else
00339                 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create login packet\n");
00340 }                               // qq_send_packet_login
00341 
00342 /*****************************************************************************/
00343 // send logout packets to QQ server
00344 void qq_send_packet_logout(GaimConnection * gc)
00345 {
00346         gint i;
00347         qq_data *qd;
00348 
00349         g_return_if_fail(gc != NULL && gc->proto_data != NULL);
00350 
00351         qd = (qq_data *) gc->proto_data;
00352         for (i = 0; i < 4; i++)
00353                 qq_send_cmd(gc, QQ_CMD_LOGOUT, FALSE, 0xffff, FALSE, qd->pwkey, QQ_KEY_LENGTH);
00354 
00355         qd->logged_in = FALSE;  // update login status AFTER sending logout packets
00356 }                               // qq_send_packet_logout
00357 
00358 /*****************************************************************************/
00359 // process the login reply packet
00360 void qq_process_login_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
00361 {
00362         gint len, ret, bytes;
00363         guint8 *data;
00364         qq_data *qd;
00365 
00366         g_return_if_fail(gc != NULL && gc->proto_data != NULL);
00367         g_return_if_fail(buf != NULL && buf_len != 0);
00368 
00369         qd = (qq_data *) gc->proto_data;
00370         len = buf_len;
00371         data = g_newa(guint8, len);     // no need to be freed in the future
00372 
00373         if (qq_crypt(DECRYPT, buf, buf_len, qd->pwkey, data, &len)) {
00374                 // should be able to decrypt with pwkey
00375                 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Decrypt login reply packet with pwkey, %d bytes\n", len);
00376                 if (data[0] == QQ_LOGIN_REPLY_OK) {
00377                         ret = _qq_process_login_ok(gc, data, len);
00378                 } else {
00379                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown login reply code : %d\n", data[0]);
00380                         ret = QQ_LOGIN_REPLY_MISC_ERROR;
00381                 }               // if QQ_LOGIN_REPLY_OK
00382         } else {                // decrypt with pwkey error
00383                 len = buf_len;  // reset len, decrypt will fail if len is too short              
00384                 if (qq_crypt(DECRYPT, buf, buf_len, qd->inikey, data, &len)) {
00385                         // decrypt ok with inipwd, it might be password error
00386                         gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Decrypt login reply packet with inikey, %d bytes data[0] is %x\n", len, data[0]);
00387                         bytes = 0;
00388                         switch (data[0]) {
00389                         case QQ_LOGIN_REPLY_REDIRECT:
00390                                 ret = _qq_process_login_redirect(gc, data, len);
00391                                 break;
00392                         case QQ_LOGIN_REPLY_PWD_ERROR:
00393                                 ret = _qq_process_login_wrong_pwd(gc, data, len);
00394                                 break;
00395                         default:
00396                                 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]);
00397                                 ret = QQ_LOGIN_REPLY_MISC_ERROR;
00398                         }       // switch data[0]
00399                 } else {        // no idea how to decrypt
00400                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "No idea how to decrypt login reply\n");
00401                         ret = QQ_LOGIN_REPLY_MISC_ERROR;
00402                 }               // if qq_crypt with qd->inikey
00403         }                       // if qq_crypt with qd->pwkey
00404 
00405         switch (ret) {
00406         case QQ_LOGIN_REPLY_PWD_ERROR:
00407                 gaim_connection_error(gc, _("Wrong password!"));
00408                 break;
00409         case QQ_LOGIN_REPLY_MISC_ERROR:
00410                 gaim_connection_error(gc, _("Unable to login, check debug log"));
00411                 break;
00412         case QQ_LOGIN_REPLY_OK:
00413                 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login replys OK, everything is fine\n");
00414                 break;
00415         case QQ_LOGIN_REPLY_REDIRECT:
00416                 // the redirect has been done in _qq_process_login_reply
00417                 break;
00418         default:{;
00419                 }
00420         }                       // switch ret
00421 }                               // qq_process_login_reply
00422 
00423 //add by Yuan Qingyun to process login token
00424 void qq_process_login_token_relay(guint8 * buf, gint buf_len, GaimConnection * gc)
00425 {
00426         gint bytes;
00427         guint8 retCode = 0;
00428         guint8 *cursor;
00429         qq_data *qd;
00430         if (NULL ==gc || NULL == gc->proto_data)
00431                 return;
00432         qd = (qq_data *) gc->proto_data;
00433         cursor = buf;
00434         bytes = 0;
00435 
00436         // 000-000: reply code
00437         bytes += read_packet_b(buf, &cursor, buf_len, &retCode);
00438         if (retCode != 0)
00439         {
00440                 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail get login tocken from server\n");
00441                 return;
00442         }
00443         bytes += read_packet_b(buf, &cursor, buf_len, &qd->token_len);
00444         if (qd->ptoken != NULL)
00445                 g_free(qd->ptoken);
00446         qd->ptoken = g_new0(guint8, qd->token_len);
00447         bytes += read_packet_data(buf, &cursor, buf_len, qd->ptoken, qd->token_len);
00448         qq_send_packet_login(gc);
00449 
00450 }
00451 
00452 //add by Yuan Qingyun for send login token
00453 void qq_send_packet_login_token(GaimConnection * gc)
00454 {
00455         qq_data *qd;
00456         guint8 *buf, *cursor;
00457         gint bytes;
00458         guint16 seq_ret;
00459         g_return_if_fail(gc != NULL && gc->proto_data != NULL);
00460         qd = (qq_data *) gc->proto_data;
00461         buf = g_newa(guint8, MAX_PACKET_SIZE);
00462         cursor = buf;
00463         qd->token_len = 0;
00464         qd->ptoken = NULL;
00465         bytes = 0;
00466         bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_GET_LOGIN_TOKEN, TRUE, &seq_ret);
00467         bytes += create_packet_dw(buf, &cursor, qd->uid);
00468         bytes += create_packet_b(buf, &cursor, 0x00);
00469         bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
00470         if (bytes == (cursor - buf))    // packet creation OK
00471                 _qq_send_packet(gc, buf, bytes, QQ_CMD_GET_LOGIN_TOKEN);
00472         else
00473                 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create login tocken packet\n");
00474 }
00475 
00476 /*****************************************************************************/
00477 // END OF FILE

Generated at Mon May 8 15:41:24 2006 for OpenQ by  doxygen 1.4.4