im.pidgin.pidgin: c51dd3dede6eda2c84e622b4ffe5f7a9e01152a0

markdoliner at pidgin.im markdoliner at pidgin.im
Sat Jan 5 02:50:43 EST 2008


-----------------------------------------------------------------
Revision: c51dd3dede6eda2c84e622b4ffe5f7a9e01152a0
Ancestor: 3a57ab8f995fc58a55ea1b51bbf10c04ff71fe70
Author: markdoliner at pidgin.im
Date: 2008-01-05T07:33:13
Branch: im.pidgin.pidgin

Modified files:
        ChangeLog libpurple/protocols/oscar/family_icbm.c
        libpurple/protocols/oscar/family_icq.c
        libpurple/protocols/oscar/oscar.c
        libpurple/protocols/oscar/oscar.h

ChangeLog: 

Partial support for reading ICQ 6 status notes.  The status note will
show up next to the person's name in the buddy list, but only if they
don't have another status message set.

This is from Collin from ComBOTS GmbH.  Fixes #3208.

-------------- next part --------------
============================================================
--- ChangeLog	56b93e9f7acafbfb6b27bfe3f1b2e23707b73dbe
+++ ChangeLog	055f35c7ae7e5b48011fd30b2a35fe0ef0fe2d34
@@ -7,6 +7,8 @@ version 2.4.0 (??/??/????):
 	* Eliminated unmaintained Howl backend implementation for the
 	  Bonjour protocol.  Avahi (or Apple's Bonjour runtime on win32) is
 	  now required to use Bonjour.
+	* Partial support for viewing ICQ status notes (Collin from
+	  ComBOTS GmbH).
 
 	Pidgin:
 	* Added the ability to theme conversation name colors (red and blue)
============================================================
--- libpurple/protocols/oscar/family_icbm.c	1f9f5f90996c9d9ee96ae8b8a894601f4f690ce2
+++ libpurple/protocols/oscar/family_icbm.c	a46859abee60456355c915cabd1ef39ad9ba7435
@@ -51,6 +51,9 @@
 #include "win32dep.h"
 #endif
 
+#include "util.h"
+
+
 /**
  * Add a standard ICBM header to the given bstream with the given
  * information.
@@ -2335,11 +2338,166 @@ static int clientautoresp(OscarData *od,
 	sn = byte_stream_getstr(bs, snlen);
 	reason = byte_stream_get16(bs);
 
-	if (channel == 0x0002) { /* File transfer declined */
+	if (channel == 0x0002)
+	{
+		/* parse status note text */
+
+		struct aim_icq_info *info = NULL;
+		struct aim_icq_info *prev_info = NULL;
+		char *response = NULL;
+		char *encoding = NULL;
+		char *stripped_encoding = NULL;
+		char *status_note_text = NULL;
+		char *stripped_status_note_text = NULL;
+		char *status_note = NULL;
+
+		/*
+		 * TODO: Using a while statement here is kind of an ugly hack
+		 *       to be able to use 'break'.  We might as well be using
+		 *       'goto'.  Should probably get rid of this.
+		 */
+		while (reason == 0x0003) /* channel-specific */
+		{
+			guint32 length;
+			guint16 version;
+			guint32 capability;
+			guint8 message_type;
+			guint16 status_code;
+			guint16 text_length;
+			guint32 request_length;
+			guint32 response_length;
+			guint32 encoding_length;
+			PurpleAccount *account;
+			PurpleBuddy *buddy;
+			PurplePresence *presence;
+			PurpleStatus *status;
+
+			for (info = od->icq_info; info != NULL; info = info->next)
+			{
+				if (memcmp(&info->icbm_cookie, cookie, 8) == 0)
+				{
+					if (prev_info == NULL)
+						od->icq_info = info->next;
+					else
+						prev_info->next = info->next;
+
+					break;
+				}
+
+				prev_info = info;
+			}
+
+			if (info == NULL)
+				break;
+
+			if ((length = byte_stream_getle16(bs)) != 27)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect header size; expected 27, received %u.\n", length);
+				break;
+			}
+			if ((version = byte_stream_getle16(bs)) != 9)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect version; expected 9, received %u.\n", version);
+				break;
+			}
+			capability = aim_locate_getcaps(od, bs, 0x10);
+			if (capability != OSCAR_CAPABILITY_EMPTY)
+			{
+				purple_debug_misc("oscar", "clientautoresp: plugin ID is not null.\n");
+				break;
+			}
+			byte_stream_advance(bs, 2); /* unknown */
+			byte_stream_advance(bs, 4); /* client capabilities flags */
+			byte_stream_advance(bs, 1); /* unknown */
+			byte_stream_advance(bs, 2); /* downcouner? */
+
+			if ((length = byte_stream_getle16(bs)) != 14)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect header size; expected 14, received %u.\n", length);
+				break;
+			}
+			byte_stream_advance(bs, 2); /* downcounter? */
+			byte_stream_advance(bs, 12); /* unknown */
+
+			if ((message_type = byte_stream_get8(bs)) != 0x1a)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect message type; expected 0x1a, received 0x%x.\n", message_type);
+				break;
+			}
+			byte_stream_advance(bs, 1); /* message flags */
+			if ((status_code = byte_stream_getle16(bs)) != 0)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect status code; expected 0, received %u.\n", status_code);
+				break;
+			}
+			byte_stream_advance(bs, 2); /* priority code */
+
+			text_length = byte_stream_getle16(bs);
+			byte_stream_advance(bs, text_length); /* text */
+
+			length = byte_stream_getle16(bs);
+			byte_stream_advance(bs, 18); /* unknown */
+			if (length != 18 + 4 + (request_length = byte_stream_getle32(bs)) + 17)
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect block; expected length is %u, got %u.\n", 18 + 4 + request_length + 17, length);
+				break;
+			}
+			byte_stream_advance(bs, request_length); /* x request */
+			byte_stream_advance(bs, 17); /* unknown */
+
+			length = byte_stream_getle32(bs);
+			response_length = byte_stream_getle32(bs);
+			response = byte_stream_getstr(bs, response_length);
+			if (length != 4 + response_length + 4 + (encoding_length = byte_stream_getle32(bs)))
+			{
+				purple_debug_misc("oscar", "clientautoresp: incorrect block; expected length is %u, got %u.\n", 4 + response_length + 4 + encoding_length, length);
+				break;
+			}
+			encoding = byte_stream_getstr(bs, encoding_length);
+
+			account = purple_connection_get_account(od->gc);
+			stripped_encoding = oscar_encoding_extract(encoding);
+			status_note_text = oscar_encoding_to_utf8(account, stripped_encoding, response, response_length);
+			stripped_status_note_text = purple_markup_strip_html(status_note_text);
+
+			if (stripped_status_note_text != NULL && stripped_status_note_text[0] != 0)
+				status_note = g_strdup_printf("%s: %s", info->status_note_title, stripped_status_note_text);
+			else
+				status_note = g_strdup(info->status_note_title);
+
+			buddy = purple_find_buddy(account, sn);
+			if (buddy == NULL)
+			{
+				purple_debug_misc("oscar", "clientautoresp: buddy %s was not found.\n", sn);
+				break;
+			}
+
+			purple_debug_misc("oscar", "clientautoresp: setting status message to \"%s\".\n", status_note);
+
+			presence = purple_buddy_get_presence(buddy);
+			status = purple_presence_get_active_status(presence);
+
+			purple_prpl_got_user_status(account, sn,
+					purple_status_get_id(status),
+					"message", status_note, NULL);
+
+			break;
+		}
+
+		g_free(status_note);
+		g_free(stripped_status_note_text);
+		g_free(status_note_text);
+		g_free(stripped_encoding);
+		g_free(encoding);
+		g_free(response);
+		g_free(info->status_note_title);
+		g_free(info);
+
 		byte_stream_get16(bs); /* Unknown */
 		byte_stream_get16(bs); /* Unknown */
 		if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
 			ret = userfunc(od, conn, frame, channel, sn, reason, cookie);
+
 	} else if (channel == 0x0004) { /* ICQ message */
 		switch (reason) {
 			case 0x0003: { /* ICQ status message.  Maybe other stuff too, you never know with these people. */
============================================================
--- libpurple/protocols/oscar/family_icq.c	c71f8b1ede389ba1208b032624c01e38ab74a1ca
+++ libpurple/protocols/oscar/family_icq.c	b9f665b30fd9fcf2136e0dfba1a0f301f2f869b1
@@ -435,6 +435,65 @@ int aim_icq_sendsms(OscarData *od, const
 	return 0;
 }
 
+/*
+ * getstatusnote may be a misleading name because the response
+ * contains a lot of different information but currently it's only
+ * used to get that.
+ */
+int aim_icq_getstatusnote(OscarData *od, const char *uin, guint8 *note_hash, guint16 note_hash_len)
+{
+	FlapConnection *conn;
+	FlapFrame *frame;
+	aim_snacid_t snacid;
+	int bslen;
+
+	purple_debug_misc("oscar", "aim_icq_getstatusnote: requesting status note for %s.\n", uin);
+
+	if (!od || !(conn = flap_connection_findbygroup(od, 0x0015)))
+	{
+		purple_debug_misc("oscar", "aim_icq_getstatusnote: no connection.\n");
+		return -EINVAL;
+	}
+
+	bslen = 2 + 4 + 2 + 2 + 2 + 2 + 58 + strlen(uin);
+
+	frame = flap_frame_new(od, 0x02, 10 + 4 + bslen);
+
+	snacid = aim_cachesnac(od, 0x0015, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&frame->data, 0x0015, 0x0002, 0x0000, snacid);
+
+	/* For simplicity, don't bother using a tlvlist */
+	byte_stream_put16(&frame->data, 0x0001);
+	byte_stream_put16(&frame->data, bslen);
+
+	byte_stream_putle16(&frame->data, bslen - 2);
+	byte_stream_putle32(&frame->data, atoi(od->sn));
+	byte_stream_putle16(&frame->data, 0x07d0); /* I command thee. */
+	byte_stream_putle16(&frame->data, snacid); /* eh. */
+	byte_stream_putle16(&frame->data, 0x0fa0); /* shrug. */
+	byte_stream_putle16(&frame->data, 58 + strlen(uin));
+
+	byte_stream_put32(&frame->data, 0x05b90002);    /* don't ask */
+	byte_stream_put32(&frame->data, 0x80000000);
+	byte_stream_put32(&frame->data, 0x00000006);
+	byte_stream_put32(&frame->data, 0x00010002);
+	byte_stream_put32(&frame->data, 0x00020000);
+	byte_stream_put32(&frame->data, 0x04e30000);
+	byte_stream_put32(&frame->data, 0x00020002);
+	byte_stream_put32(&frame->data, 0x00000001);
+
+	byte_stream_put16(&frame->data, 24 + strlen(uin));
+	byte_stream_put32(&frame->data, 0x003c0010);
+	byte_stream_putraw(&frame->data, note_hash, 16); /* status note hash */
+	byte_stream_put16(&frame->data, 0x0032);        /* buddy uin */
+	byte_stream_put16(&frame->data, strlen(uin));
+	byte_stream_putstr(&frame->data, uin);
+
+	flap_connection_send(conn, frame);
+
+	return 0;
+}
+
 static void aim_icq_freeinfo(struct aim_icq_info *info) {
 	int i;
 
@@ -467,6 +526,7 @@ static void aim_icq_freeinfo(struct aim_
 	g_free(info->workposition);
 	g_free(info->workwebpage);
 	g_free(info->info);
+	g_free(info->status_note_title);
 	g_free(info);
 }
 
@@ -641,6 +701,178 @@ icqresponse(OscarData *od, FlapConnectio
 			info->email = byte_stream_getstr(&qbs, byte_stream_getle16(&qbs));
 			/* Then 0x00 02 00 00 00 00 00 */
 		} break;
+
+		/* status note title and send request for status note text */
+		case 0x0fb4: {
+			GSList *tlvlist;
+			aim_tlv_t *tlv;
+			FlapConnection *conn;
+			char *uin = NULL;
+			char *status_note_title = NULL;
+
+			conn = flap_connection_findbygroup(od, 0x0004);
+			if (conn == NULL)
+			{
+				purple_debug_misc("oscar", "icq/0x0fb4: flap connection was not found.\n");
+				break;
+			}
+
+			byte_stream_advance(&qbs, 0x02); /* length */
+			byte_stream_advance(&qbs, 0x2f); /* unknown stuff */
+
+			tlvlist = aim_tlvlist_read(&qbs);
+
+			tlv = aim_tlv_gettlv(tlvlist, 0x0032, 1);
+			if (tlv != NULL)
+				/* Get user number */
+				uin = aim_tlv_getvalue_as_string(tlv);
+
+			tlv = aim_tlv_gettlv(tlvlist, 0x0226, 1);
+			if (tlv != NULL)
+				/* Get status note title */
+				status_note_title = aim_tlv_getvalue_as_string(tlv);
+
+			aim_tlvlist_free(tlvlist);
+
+			if (uin == NULL || status_note_title == NULL)
+			{
+				purple_debug_misc("oscar", "icq/0x0fb4: uin or "
+						"status_note_title was not found\n");
+				g_free(uin);
+				g_free(status_note_title);
+				break;
+			}
+
+			if (status_note_title[0] == '\0')
+			{
+				PurpleAccount *account;
+				PurpleBuddy *buddy;
+				PurplePresence *presence;
+				PurpleStatus *status;
+
+				account = purple_connection_get_account(od->gc);
+				buddy = purple_find_buddy(account, uin);
+				presence = purple_buddy_get_presence(buddy);
+				status = purple_presence_get_active_status(presence);
+
+				purple_prpl_got_user_status(account, uin,
+						purple_status_get_id(status),
+						"message", NULL, NULL);
+
+				g_free(status_note_title);
+			}
+			else
+			{
+				struct aim_icq_info *info;
+				guint32 data_len;
+				FlapFrame *frame;
+				aim_snacid_t snacid;
+				guchar cookie[8];
+
+				info = g_new0(struct aim_icq_info, 1);
+
+				if (info == NULL)
+				{
+					g_free(uin);
+					g_free(status_note_title);
+
+					break;
+				}
+
+				data_len = 13 + strlen(uin) + 30 + 6 + 4 + 55 + 85 + 4;
+				frame = flap_frame_new(od, 0x0002, 10 + 4 + data_len);
+				snacid = aim_cachesnac(od, 0x0004, 0x0006, 0x0000, NULL, 0);
+
+				aim_putsnac(&frame->data, 0x0004, 0x0006, 0x0000, snacid);
+
+				aim_icbm_makecookie(cookie);
+
+				byte_stream_putraw(&frame->data, cookie, 8); /* ICBM cookie */
+				byte_stream_put16(&frame->data, 0x0002); /* message channel */
+				byte_stream_put8(&frame->data, strlen(uin)); /* uin */
+				byte_stream_putstr(&frame->data, uin);
+
+				byte_stream_put16(&frame->data, 0x0005); /* rendez vous data */
+				byte_stream_put16(&frame->data, 0x00b2);
+				byte_stream_put16(&frame->data, 0x0000); /* request */
+				byte_stream_putraw(&frame->data, cookie, 8); /* ICBM cookie */
+				byte_stream_put32(&frame->data, 0x09461349); /* ICQ server relaying */
+				byte_stream_put16(&frame->data, 0x4c7f);
+				byte_stream_put16(&frame->data, 0x11d1);
+				byte_stream_put32(&frame->data, 0x82224445);
+				byte_stream_put32(&frame->data, 0x53540000);
+
+				byte_stream_put16(&frame->data, 0x000a); /* unknown TLV */
+				byte_stream_put16(&frame->data, 0x0002);
+				byte_stream_put16(&frame->data, 0x0001);
+
+				byte_stream_put16(&frame->data, 0x000f); /* unknown TLV */
+				byte_stream_put16(&frame->data, 0x0000);
+
+				byte_stream_put16(&frame->data, 0x2711); /* extended data */
+				byte_stream_put16(&frame->data, 0x008a);
+				byte_stream_putle16(&frame->data, 0x001b); /* length */
+				byte_stream_putle16(&frame->data, 0x0009); /* version */
+				byte_stream_putle32(&frame->data, 0x00000000); /* plugin: none */
+				byte_stream_putle32(&frame->data, 0x00000000);
+				byte_stream_putle32(&frame->data, 0x00000000);
+				byte_stream_putle32(&frame->data, 0x00000000);
+				byte_stream_putle16(&frame->data, 0x0000); /* unknown */
+				byte_stream_putle32(&frame->data, 0x00000000); /* client capabilities flags */
+				byte_stream_put8(&frame->data, 0x00); /* unknown */
+				byte_stream_putle16(&frame->data, 0x0064); /* downcounter? */
+				byte_stream_putle16(&frame->data, 0x000e); /* length */
+				byte_stream_putle16(&frame->data, 0x0064); /* downcounter? */
+				byte_stream_putle32(&frame->data, 0x00000000); /* unknown */
+				byte_stream_putle32(&frame->data, 0x00000000);
+				byte_stream_putle32(&frame->data, 0x00000000);
+				byte_stream_put8(&frame->data, 0x1a); /* message type: plugin message descibed by text string */
+				byte_stream_put8(&frame->data, 0x00); /* message flags */
+				byte_stream_putle16(&frame->data, 0x0000); /* status code */
+				byte_stream_putle16(&frame->data, 0x0001); /* priority code */
+				byte_stream_putle16(&frame->data, 0x0000); /* text length */
+
+				byte_stream_put8(&frame->data, 0x3a); /* message dump */
+				byte_stream_put32(&frame->data, 0x00811a18);
+				byte_stream_put32(&frame->data, 0xbc0e6c18);
+				byte_stream_put32(&frame->data, 0x47a5916f);
+				byte_stream_put32(&frame->data, 0x18dcc76f);
+				byte_stream_put32(&frame->data, 0x1a010013);
+				byte_stream_put32(&frame->data, 0x00000041);
+				byte_stream_put32(&frame->data, 0x77617920);
+				byte_stream_put32(&frame->data, 0x53746174);
+				byte_stream_put32(&frame->data, 0x7573204d);
+				byte_stream_put32(&frame->data, 0x65737361);
+				byte_stream_put32(&frame->data, 0x67650100);
+				byte_stream_put32(&frame->data, 0x00000000);
+				byte_stream_put32(&frame->data, 0x00000000);
+				byte_stream_put32(&frame->data, 0x00000000);
+				byte_stream_put32(&frame->data, 0x00000015);
+				byte_stream_put32(&frame->data, 0x00000000);
+				byte_stream_put32(&frame->data, 0x0000000d);
+				byte_stream_put32(&frame->data, 0x00000074);
+				byte_stream_put32(&frame->data, 0x6578742f);
+				byte_stream_put32(&frame->data, 0x782d616f);
+				byte_stream_put32(&frame->data, 0x6c727466);
+
+				byte_stream_put16(&frame->data, 0x0003); /* server ACK requested */
+				byte_stream_put16(&frame->data, 0x0000);
+
+				info->uin = atoi(uin);
+				info->status_note_title = status_note_title;
+
+				memcpy(&info->icbm_cookie, cookie, 8);
+
+				info->next = od->icq_info;
+				od->icq_info = info;
+
+				flap_connection_send(conn, frame);
+			}
+
+			g_free(uin);
+
+		} break;
+
 		} /* End switch statement */
 
 		if (!(snac->flags & 0x0001)) {
============================================================
--- libpurple/protocols/oscar/oscar.c	f559882f8877704e5a34a9d161d144a1b7ecbea9
+++ libpurple/protocols/oscar/oscar.c	30ca69204696b1ba41ad22e004dc18359f86492b
@@ -1879,6 +1879,31 @@ static int purple_parse_oncoming(OscarDa
 		g_free(b16);
 	}
 
+	/*
+	 * If we didn't receive a status message with the status change,
+	 * or if the message is empty, and we have a note hash, then
+	 * query the ICQ6 status note.
+	 *
+	 * TODO: We should probably always query the status note regardless
+	 *       of whether they have a status message set, and we should
+	 *       figure out a way to display both the status note and the
+	 *       status message at the same time.
+	 */
+	if (info->status == NULL || info->status[0] == '\0')
+	{
+		struct aim_ssi_item *ssi_item;
+		aim_tlv_t *note_hash;
+
+		ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
+				NULL, info->sn, AIM_SSI_TYPE_BUDDY);
+		if (ssi_item != NULL)
+		{
+			note_hash = aim_tlv_gettlv(ssi_item->data, 0x015c, 1);
+			if (note_hash != NULL)
+				aim_icq_getstatusnote(od, info->sn, note_hash->value, note_hash->length);
+		}
+	}
+
 	return 1;
 }
 
@@ -4520,12 +4545,11 @@ oscar_set_info_and_status(PurpleAccount 
 		/* This is needed for us to un-set any previous away message. */
 		away = g_strdup("");
 	}
-	else if ((primitive == PURPLE_STATUS_AWAY) ||
-			 (primitive == PURPLE_STATUS_EXTENDED_AWAY))
+	else
 	{
 		htmlaway = purple_status_get_attr_string(status, "message");
 		if ((htmlaway == NULL) || (*htmlaway == '\0'))
-			htmlaway = _("Away");
+			htmlaway = purple_status_type_get_name(status_type);
 		away = purple_prpl_oscar_convert_to_infotext(htmlaway, &awaylen, &away_encoding);
 
 		if (awaylen > od->rights.maxawaymsglen)
@@ -5099,6 +5123,8 @@ purple_ssi_parseaddmod(OscarData *od, Fl
 	char *gname, *gname_utf8, *alias, *alias_utf8;
 	PurpleBuddy *b;
 	PurpleGroup *g;
+	struct aim_ssi_item *ssi_item;
+	aim_tlv_t *note_hash;
 	va_list ap;
 	guint16 snac_subtype, type;
 	const char *name;
@@ -5166,6 +5192,21 @@ purple_ssi_parseaddmod(OscarData *od, Fl
 
 	}
 
+	ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
+			gname, name, AIM_SSI_TYPE_BUDDY);
+	if (ssi_item != NULL)
+	{
+		note_hash = aim_tlv_gettlv(ssi_item->data, 0x015c, 1);
+		if (note_hash != NULL)
+			aim_icq_getstatusnote(od, name, note_hash->value, note_hash->length);
+	}
+	else
+	{
+		purple_debug_error("oscar", "purple_ssi_parseaddmod: "
+				"Could not find ssi item for oncoming buddy %s, "
+				"group %s\n", name, gname);
+	}
+
 	g_free(gname_utf8);
 	g_free(alias_utf8);
 
============================================================
--- libpurple/protocols/oscar/oscar.h	fa0835ae56a2f99744a30266ac463dc31659375d
+++ libpurple/protocols/oscar/oscar.h	6ca115dd937ae0fc4374216413ddff6f53e38a1d
@@ -1,10 +1,8 @@
 /*
  * Purple's oscar protocol plugin
  * This file is the legal property of its developers.
  * Please see the AUTHORS file distributed alongside this file.
  *
- * Some code copyright (C) 2007, ComBOTS Product GmbH (htfv) <foss at combots.com>
- *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
@@ -1329,6 +1327,10 @@ struct aim_icq_info
 
 	/* we keep track of these in a linked list because we're 1337 */
 	struct aim_icq_info *next;
+
+	/* status note info */
+	guint8 icbm_cookie[8];
+	char *status_note_title;
 };
 
 int aim_icq_reqofflinemsgs(OscarData *od);
@@ -1339,9 +1341,9 @@ int aim_icq_sendsms(OscarData *od, const
 int aim_icq_getalias(OscarData *od, const char *uin);
 int aim_icq_getallinfo(OscarData *od, const char *uin);
 int aim_icq_sendsms(OscarData *od, const char *name, const char *msg, const char *alias);
+int aim_icq_getstatusnote(OscarData *od, const char *uin, guint8 *note_hash, guint16 note_hash_len);
 
 
-
 /* 0x0017 - family_auth.c */
 void aim_sendcookie(OscarData *, FlapConnection *, const guint16 length, const guint8 *);
 int aim_admin_changepasswd(OscarData *, FlapConnection *, const char *newpw, const char *curpw);


More information about the Commits mailing list