diff --git a/Makefile b/Makefile index 659dd2d..60f4fe1 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ endif RM=rm -f FIND=find INCLUDES=-Ilibstrophe $(shell xml2-config --cflags) $(shell pkg-config --cflags librnp-0) $(shell pkg-config --cflags libsignal-protocol-c) -CFLAGS+=$(DBGCFLAGS) -fno-omit-frame-pointer -fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -D_XOPEN_SOURCE=700 $(INCLUDES) +CFLAGS+=$(DBGCFLAGS) -fno-omit-frame-pointer -fPIC -std=gnu99 -gdwarf-4 -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -D_XOPEN_SOURCE=700 $(INCLUDES) LDFLAGS+=$(DBGLDFLAGS) -shared -g $(DBGCFLAGS) LDLIBS=-lstrophe -lpthread $(shell xml2-config --libs) $(shell pkg-config --libs librnp-0) $(shell pkg-config --libs libsignal-protocol-c) -lgcrypt diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..45daedf --- /dev/null +++ b/NOTES.md @@ -0,0 +1,145 @@ +βœ—: No support +β§—: Planned support +βœ“: Partial support +?: Supported, but status not specified +βœ“: Complete support +πŸ•‡: Removed support +☠: Client will never support this XEP ++-------------------------------------------------------------------------+------------------------+---------------+------+-------+----------+-------+--------+-------+ +| XEP | Bruno the Jabberβ„’ Bear | Conversations | Dino | Gajim | Monal IM | Movim | Poezio | yaxim | ++-------------------------------------------------------------------------+------------------------+---------------+------+-------+----------+-------+--------+-------+ +| 0004: Data Forms(Final) | βœ— | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0012: Last Activity(Final) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ“ | βœ— | +| 0027: Current Jabber OpenPGP Usage(Obsolete) | βœ— | βœ“ | βœ“ | βœ“ | ☠ | βœ— | βœ“ | βœ— | +| 0030: Service Discovery(Final) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0033: Extended Stanza Addressing(Draft) | βœ— | βœ— | βœ— | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0045: Multi-User Chat(Draft) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0047: In-Band Bytestreams(Final) | βœ— | βœ— | ? | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0048: Bookmarks(Deprecated) | βœ“ | βœ“ | ? | βœ“ | β§— | βœ“ | βœ“ | βœ“ | +| 0049: Private XML Storage(Active) | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | +| 0050: Ad-Hoc Commands(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0054: vcard-temp(Active) | βœ— | βœ“ | βœ“ | βœ“ | ☠ | βœ“ | βœ“ | βœ— | +| 0055: Jabber Search(Active) | βœ— | βœ— | βœ— | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0059: Result Set Management(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ“ | βœ“ | βœ— | βœ— | +| 0060: Publish-Subscribe(Draft) | βœ— | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0065: SOCKS5 Bytestreams(Draft) | βœ— | βœ— | βœ— | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0066: Out of Band Data(Draft) | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | βœ— | βœ— | βœ“ | +| 0070: Verifying HTTP Requests via XMPP(Draft) | βœ— | βœ— | βœ— | βœ“ | ☠ | βœ“ | βœ“ | βœ— | +| 0071: XHTML-IM(Deprecated) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0077: In-Band Registration(Final) | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0080: User Location(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0082: XMPP Date and Time Profiles(Active) | βœ— | βœ— | βœ“ | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0083: Nested Roster Groups(Active) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0084: User Avatar(Draft) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0085: Chat State Notifications(Final) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0091: Legacy Delayed Delivery(Obsolete) | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ“ | +| 0092: Software Version(Draft) | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0095: Stream Initiation(Deprecated) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0100: Gateway Interaction(Active) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0106: JID Escaping(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0107: User Mood(Draft) | βœ— | βœ— | βœ— | βœ“ | ☠ | βœ“ | βœ“ | βœ— | +| 0108: User Activity(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0115: Entity Capabilities(Draft) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0118: User Tune(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0144: Roster Item Exchange(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0145: Annotations(Active) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0146: Remote Controlling Clients(Obsolete) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0147: XMPP URI Scheme Query Components(Active) | βœ“ | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ“ | +| 0153: vCard-Based Avatars(Active) | βœ— | βœ“ | ? | βœ“ | ☠ | βœ“ | βœ“ | βœ— | +| 0156: Discovering Alternative XMPP Connection Methods(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0157: Contact Addresses for XMPP Services(Active) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0158: CAPTCHA Forms(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ“ | βœ— | βœ— | βœ— | +| 0162: Best Practices for Roster and Subscription Management(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | +| 0163: Personal Eventing Protocol(Draft) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0166: Jingle(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0167: Jingle RTP Sessions(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0172: User Nickname(Draft) | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0174: Serverless Messaging(Final) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0175: Best Practices for Use of SASL ANONYMOUS(Active) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | βœ— | +| 0176: Jingle ICE-UDP Transport Method(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0178: Best Practices for Use of SASL EXTERNAL with Certificates(Active) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | βœ— | +| 0184: Message Delivery Receipts(Draft) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0191: Blocking Command(Draft) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ“ | βœ— | +| 0196: User Gaming(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | +| 0198: Stream Management(Draft) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | +| 0199: XMPP Ping(Final) | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | +| 0200: Stanza Encryption(Deferred) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0202: Entity Time(Final) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | βœ— | +| 0203: Delayed Delivery(Final) | βœ“ | βœ— | βœ“ | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | +| 0209: Metacontacts(Deferred) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0215: External Service Discovery(Deferred) | βœ— | βœ“ | ? | βœ— | β§— | βœ“ | βœ— | βœ— | +| 0221: Data Forms Media Element(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0222: Persistent Storage of Public Data via PubSub(Active) | βœ— | βœ— | ? | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0223: Persistent Storage of Private Data via PubSub(Active) | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | βœ— | βœ— | +| 0224: Attention(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | βœ— | +| 0231: Bits of Binary(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0234: Jingle File Transfer(Deferred) | βœ— | βœ“ | ? | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0237: Roster Versioning(Obsolete) | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ— | βœ— | βœ— | +| 0245: The /me Command(Active) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0249: Direct MUC Invitations(Draft) | βœ“ | βœ“ | ? | βœ“ | β§— | βœ— | βœ“ | βœ“ | +| 0256: Last Activity in Presence(Draft) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0257: Client Certificate Management for SASL EXTERNAL(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | +| 0258: Security Labels in XMPP(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0260: Jingle SOCKS5 Bytestreams Transport Method(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0261: Jingle In-Band Bytestreams Transport Method(Draft) | βœ— | βœ“ | βœ“ | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0277: Microblogging over XMPP(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0280: Message Carbons(Experimental) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0284: Shared XML Editing(Deferred) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0286: Mobile Considerations on LTE Networks(Active) | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | +| 0292: vCard4 Over XMPP(Deferred) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ— | βœ— | +| 0293: Jingle RTP Feedback Negotiation(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0294: Jingle RTP Header Extensions Negotiation(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0296: Best Practices for Resource Locking(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | +| 0297: Stanza Forwarding(Draft) | βœ“ | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | +| 0300: Use of Cryptographic Hash Functions in XMPP(Draft) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0306: Extensible Status Conditions for Multi-User Chat(Deferred) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0308: Last Message Correction(Draft) | βœ“ | βœ“ | ? | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0313: Message Archive Management(Experimental) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0319: Last User Interaction in Presence(Draft) | βœ— | βœ“ | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | +| 0320: Use of DTLS-SRTP in Jingle Sessions(Draft) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0330: Pubsub Subscription(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0333: Chat Markers(Deferred) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ— | +| 0334: Message Processing Hints(Deferred) | βœ— | βœ— | ? | βœ“ | βœ— | βœ“ | βœ“ | βœ— | +| 0338: Jingle Grouping Framework(Draft) | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0339: Source-Specific Media Attributes in Jingle(Draft) | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0343: Signaling WebRTC datachannels in Jingle(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0352: Client State Indication(Draft) | βœ“ | βœ“ | βœ— | βœ— | βœ“ | βœ— | βœ“ | βœ“ | +| 0353: Jingle Message Initiation(Deferred) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0357: Push Notifications(Deferred) | βœ“ | βœ“ | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | +| 0359: Unique and Stable Stanza IDs(Deferred) | βœ“ | βœ— | ? | βœ“ | βœ— | βœ“ | βœ— | βœ“ | +| 0363: HTTP File Upload(Draft) | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | +| 0364: Current Off-the-Record Messaging Usage(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | +| 0367: Message Attaching(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0368: SRV records for XMPP over TLS(Draft) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ— | +| 0369: Mediated Information eXchange (MIX)(Experimental) | βœ— | βœ— | βœ— | βœ— | β§— | βœ— | βœ— | βœ— | +| 0372: References(Experimental) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0373: OpenPGP for XMPP(Experimental) | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0374: OpenPGP for XMPP Instant Messaging(Deferred) | βœ— | βœ— | βœ— | βœ— | β§— | βœ— | βœ— | βœ— | +| 0377: Spam Reporting(Experimental) | βœ— | βœ“ | βœ— | βœ“ | ☠ | βœ— | βœ— | βœ— | +| 0378: OTR Discovery(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | +| 0379: Pre-Authenticated Roster Subscription(Deferred) | βœ“ | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ“ | +| 0380: Explicit Message Encryption(Deferred) | βœ— | βœ— | βœ“ | βœ“ | β§— | βœ“ | βœ“ | βœ— | +| 0384: OMEMO Encryption(Experimental) | βœ— | βœ“ | βœ“ | βœ“ | βœ“ | βœ— | βœ“ | βœ— | +| 0385: Stateless Inline Media Sharing (SIMS)(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0386: Bind 2.0(Deferred) | βœ— | βœ— | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0390: Entity Capabilities 2.0(Deferred) | βœ— | βœ— | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0391: Jingle Encrypted Transports(Deferred) | βœ— | βœ“ | ? | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0392: Consistent Color Generation(Deferred) | βœ“ | βœ“ | βœ— | βœ“ | β§— | βœ— | βœ“ | βœ“ | +| 0393: Message Styling(Draft) | βœ“ | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | +| 0396: Jingle Encrypted Transports - OMEMO(Deferred) | βœ— | βœ“ | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0397: Instant Stream Resumption(Deferred) | βœ— | βœ— | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0398: User Avatar to vCard-Based Avatars Conversion(Deferred) | βœ— | βœ“ | βœ“ | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0401: Easy User Onboarding(Deferred) | βœ“ | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | +| 0402: PEP Native Bookmarks(Draft) | βœ— | βœ— | βœ— | βœ— | β§— | βœ“ | βœ— | βœ— | +| 0409: IM Routing-NG(Deferred) | βœ— | βœ— | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0410: MUC Self-Ping (Schrâdinger's Chat)(Draft) | βœ“ | βœ“ | ? | βœ— | ☠ | βœ— | βœ— | βœ“ | +| 0411: Bookmarks Conversion(Draft) | βœ— | βœ“ | βœ— | βœ“ | βœ— | βœ— | βœ— | βœ— | +| 0420: Stanza Content Encryption(Experimental) | βœ— | βœ— | βœ— | βœ— | ☠ | βœ— | βœ— | βœ— | +| 0422: Message Fastening(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0423: XMPP Compliance Suites 2020(Obsolete) | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | βœ— | +| 0424: Message Retraction(Deferred) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ“ | βœ— | βœ— | +| 0441: Message Archive Management Preferences(Experimental) | βœ— | βœ— | βœ— | βœ“ | βœ“ | βœ— | βœ— | βœ— | +| 0447: Stateless file sharing(Experimental) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0449: Stickers(Experimental) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | +| 0454: OMEMO Media sharing(Experimental) | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | βœ— | ++-------------------------------------------------------------------------+------------------------+---------------+------+-------+----------+-------+--------+-------+ diff --git a/README.org b/README.org index d1ab7a2..1a2959e 100644 --- a/README.org +++ b/README.org @@ -28,6 +28,8 @@ A weechat plugin in C to extend the chat client to support XMPP and a currently minimal but ideally maximal set of XEPs. + I'm gonna rewrite this in C++ at some point when I have a feel + for the full behaviour of an average client. * Usage @@ -44,9 +46,8 @@ - libstrophe (dynamic, dependency) - libxml2 (dynamic, dependency) - - glib (dynamic, dependency of libomemo) - - sqlite (dynamic, dependency of libomemo/axc) - - libsignal-protocol-c (dynamic, dependency of axc) + - libsignal-protocol-c (dynamic, dependency) + - rnp (dynamic, dependency) - weechat (>= v3.0) .. or just use the guix spec in .envrc @@ -125,11 +126,14 @@ * [ ] Announce * [ ] Messages * [ ] [#C] MUC PMs - * [ ] [#A] Send typing notifications - * [ ] [#A] Recv typing notifications - * [ ] [#C] Read receipts - * [ ] Message Carbons + * [X] [#A] Send typing notifications + * [X] [#A] Recv typing notifications + * [X] [#C] Read receipts + * [X] Chat Markers (XEP-0333) + * [X] Message Delivery (XEP-0184) + * [X] Message Carbons * [ ] Service Disco + * [X] MAM Fetching * [-] Bookmarks / Roster * [X] Autojoin bookmarks * [ ] Add bookmarks @@ -142,10 +146,10 @@ * [X] Decryption * [X] Encryption * [X] Custom set/clear key (/pgp) + * [ ] OOB data and media * [ ] Room Explorer (https://search.jabber.network/docs/api) ** TODO [#C] Implement completion engine (milestone v0.3) ** TODO [#D] Close all issues (milestone v1.0) - * [ ] Absorb libomemo / axc, and drop glib * Contributing diff --git a/account.c b/account.c index cb437e9..3e4a213 100644 --- a/account.c +++ b/account.c @@ -90,9 +90,9 @@ int account__search_option(const char *option_name) return -1; } -struct t_device *account__search_device(struct t_account *account, int id) +struct t_account_device *account__search_device(struct t_account *account, int id) { - struct t_device *ptr_device; + struct t_account_device *ptr_device; if (!account) return NULL; @@ -108,9 +108,9 @@ struct t_device *account__search_device(struct t_account *account, int id) } void account__add_device(struct t_account *account, - struct t_device *device) + struct t_account_device *device) { - struct t_device *new_device; + struct t_account_device *new_device; new_device = account__search_device(account, device->id); if (!new_device) @@ -129,9 +129,9 @@ void account__add_device(struct t_account *account, } } -void account__free_device(struct t_account *account, struct t_device *device) +void account__free_device(struct t_account *account, struct t_account_device *device) { - struct t_device *new_devices; + struct t_account_device *new_devices; if (!account || !device) return; diff --git a/account.h b/account.h index b66a20a..575996f 100644 --- a/account.h +++ b/account.h @@ -64,13 +64,13 @@ enum t_account_option #define account_pgp_keyid(account) \ weechat_config_string(account->options[ACCOUNT_OPTION_PGP_KEYID]) -struct t_device +struct t_account_device { int id; char *name; - struct t_device *prev_device; - struct t_device *next_device; + struct t_account_device *prev_device; + struct t_account_device *next_device; }; struct t_account @@ -97,8 +97,8 @@ struct t_account struct t_omemo *omemo; struct t_pgp *pgp; - struct t_device *devices; - struct t_device *last_device; + struct t_account_device *devices; + struct t_account_device *last_device; struct t_user *users; struct t_user *last_user; struct t_channel *channels; @@ -113,9 +113,9 @@ extern char *account_options[][2]; struct t_account *account__search(const char *account_name); struct t_account *account__casesearch (const char *account_name); int account__search_option(const char *option_name); -struct t_device *account__search_device(struct t_account *account, int id); -void account__add_device(struct t_account *account, struct t_device *device); -void account__free_device(struct t_account *account, struct t_device *device); +struct t_account_device *account__search_device(struct t_account *account, int id); +void account__add_device(struct t_account *account, struct t_account_device *device); +void account__free_device(struct t_account *account, struct t_account_device *device); void account__free_device_all(struct t_account *account); struct t_account *account__alloc(const char *name); void account__free_data(struct t_account *account); diff --git a/channel.c b/channel.c index ab918ab..4932079 100644 --- a/channel.c +++ b/channel.c @@ -19,6 +19,21 @@ #include "buffer.h" #include "pgp.h" +const char *channel__transport_name(enum t_channel_transport transport) +{ + switch (transport) + { + case CHANNEL_TRANSPORT_PLAINTEXT: + return "PLAINTEXT"; + case CHANNEL_TRANSPORT_OMEMO: + return "OMEMO"; + case CHANNEL_TRANSPORT_PGP: + return "PGP"; + default: + return NULL; + } +} + struct t_account *channel__account(struct t_channel *channel) { struct t_account *ptr_account; @@ -158,6 +173,7 @@ struct t_gui_buffer *channel__create_buffer(struct t_account *account, account_nickname(account)); weechat_buffer_set(ptr_buffer, "localvar_set_server", account->name); weechat_buffer_set(ptr_buffer, "localvar_set_channel", name); + weechat_buffer_set(ptr_buffer, "input_multiline", "1"); if (buffer_created) { @@ -240,6 +256,7 @@ struct t_channel *channel__new(struct t_account *account, new_channel->type = type; new_channel->id = strdup(id); new_channel->name = strdup(name); + new_channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; new_channel->pgp_id = NULL; new_channel->topic.value = NULL; @@ -255,6 +272,7 @@ struct t_channel *channel__new(struct t_account *account, new_channel->self_typing_hook_timer = self_typing_timer; new_channel->members_speaking[0] = NULL; new_channel->members_speaking[1] = NULL; + new_channel->unreads = NULL; new_channel->self_typings = NULL; new_channel->last_self_typing = NULL; new_channel->typings = NULL; @@ -272,6 +290,15 @@ struct t_channel *channel__new(struct t_account *account, account->channels = new_channel; account->last_channel = new_channel; + if (type != CHANNEL_TYPE_MUC) + { + time_t start = time(NULL); + struct tm *ago = gmtime(&start); + ago->tm_mday -= 7; + start = mktime(ago); + channel__fetch_mam(account, new_channel, &start, NULL); + } + return new_channel; } @@ -356,7 +383,7 @@ void channel__member_speaking_rename_if_present(struct t_account *account, list_size = weechat_list_size(channel->members_speaking[i]); for (j = 0; j < list_size; j++) { - ptr_item = weechat_list_get (channel->members_speaking[i], j); + ptr_item = weechat_list_get(channel->members_speaking[i], j); if (ptr_item && (weechat_strcasecmp(weechat_list_string(ptr_item), nick) == 0)) weechat_list_set(ptr_item, nick); @@ -629,6 +656,41 @@ int channel__add_self_typing(struct t_channel *channel, return ret; } +void channel__unread_free(struct t_channel_unread *unread) +{ + if (!unread) + return; + + if (unread->id) + free(unread->id); + if (unread->thread) + free(unread->thread); + free(unread); +} + +void channel__unread_free_all(struct t_channel *channel) +{ + if (channel->unreads) + { + int list_size = weechat_list_size(channel->unreads); + + for (int i = 0; i < list_size; i++) + { + struct t_weelist_item *ptr_item = weechat_list_get(channel->unreads, i); + if (ptr_item) + { + struct t_channel_unread *unread = weechat_list_user_data(ptr_item); + + channel__unread_free(unread); + + weechat_list_remove(channel->unreads, ptr_item); + } + } + + weechat_list_free(channel->unreads); + } +} + void channel__member_free(struct t_channel *channel, struct t_channel_member *member) { @@ -702,6 +764,7 @@ void channel__free(struct t_account *account, channel__self_typing_free_all(channel); channel__typing_free_all(channel); channel__member_free_all(channel); + channel__unread_free_all(channel); /* free channel data */ if (channel->id) @@ -923,10 +986,17 @@ struct t_channel_member *channel__remove_member(struct t_account *account, void channel__send_message(struct t_account *account, struct t_channel *channel, const char *to, const char *body) { + channel__send_reads(account, channel); + xmpp_stanza_t *message = xmpp_message_new(account->context, channel->type == CHANNEL_TYPE_MUC ? "groupchat" : "chat", to, NULL); + + char *id = xmpp_uuid_gen(account->context); + xmpp_stanza_set_id(message, id); + xmpp_free(account->context, id); + xmpp_message_set_body(message, body); char *url = strstr(body, "http"); @@ -950,8 +1020,22 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, xmpp_message_set_body(message, PGP_ADVICE); - weechat_printf(channel->buffer, "[~]\t%s%s: PGP", weechat_color("gray"), account_jid(account)); + if (ciphertext && channel->transport != CHANNEL_TRANSPORT_PGP) + { + channel->transport = CHANNEL_TRANSPORT_PGP; + weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s", + weechat_prefix("network"), weechat_color("gray"), + channel__transport_name(channel->transport)); + } + } + else if (channel->transport != CHANNEL_TRANSPORT_PLAINTEXT) + { + channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; + weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s", + weechat_prefix("network"), weechat_color("gray"), + channel__transport_name(channel->transport)); } + if (url) { xmpp_stanza_t *message__x = xmpp_stanza_new(account->context); @@ -983,6 +1067,55 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, body); } +void channel__send_reads(struct t_account *account, struct t_channel *channel) +{ + if (channel && channel->unreads) + { + int list_size = weechat_list_size(channel->unreads); + + for (int i = 0; i < list_size; i++) + { + struct t_weelist_item *ptr_item = weechat_list_get(channel->unreads, 0); + if (ptr_item) + { + const char *unread_id = weechat_list_string(ptr_item); + struct t_channel_unread *unread = weechat_list_user_data(ptr_item); + + xmpp_stanza_t *message = xmpp_message_new(account->context, NULL, + channel->id, NULL); + + xmpp_stanza_t *message__displayed = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__displayed, "displayed"); + xmpp_stanza_set_ns(message__displayed, "urn:xmpp:chat-markers:0"); + xmpp_stanza_set_id(message__displayed, unread->id ? unread->id : unread_id); + if (unread->thread) + { + xmpp_stanza_t *message__thread = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__thread, "thread"); + + xmpp_stanza_t *message__thread__text = xmpp_stanza_new(account->context); + xmpp_stanza_set_text(message__thread__text, unread->thread); + xmpp_stanza_add_child(message__thread, message__thread__text); + xmpp_stanza_release(message__thread__text); + + xmpp_stanza_add_child(message, message__thread); + xmpp_stanza_release(message__thread); + } + + xmpp_stanza_add_child(message, message__displayed); + xmpp_stanza_release(message__displayed); + + xmpp_send(account->connection, message); + xmpp_stanza_release(message); + + channel__unread_free(unread); + + weechat_list_remove(channel->unreads, ptr_item); + } + } + } +} + void channel__send_typing(struct t_account *account, struct t_channel *channel, struct t_user *user) { @@ -1023,3 +1156,116 @@ void channel__send_paused(struct t_account *account, struct t_channel *channel, xmpp_send(account->connection, message); xmpp_stanza_release(message); } + +void channel__fetch_mam(struct t_account *account, struct t_channel *channel, + time_t *start, time_t *end) +{ + xmpp_stanza_t *iq = xmpp_iq_new(account->context, "set", "juliet1"); + + xmpp_stanza_t *query = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, "urn:xmpp:mam:2"); + + xmpp_stanza_t *x = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(x, "x"); + xmpp_stanza_set_ns(x, "jabber:x:data"); + xmpp_stanza_set_attribute(x, "type", "result"); + + xmpp_stanza_t *field, *value, *text; + + { + field = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(field, "field"); + xmpp_stanza_set_attribute(field, "var", "FORM_TYPE"); + xmpp_stanza_set_attribute(field, "type", "hidden"); + + value = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(value, "value"); + + text = xmpp_stanza_new(account->context); + xmpp_stanza_set_text(text, "urn:xmpp:mam:2"); + xmpp_stanza_add_child(value, text); + xmpp_stanza_release(text); + + xmpp_stanza_add_child(field, value); + xmpp_stanza_release(value); + + xmpp_stanza_add_child(x, field); + xmpp_stanza_release(field); + } + + { + field = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(field, "field"); + xmpp_stanza_set_attribute(field, "var", "with"); + + value = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(value, "value"); + + text = xmpp_stanza_new(account->context); + xmpp_stanza_set_text(text, channel->id); + xmpp_stanza_add_child(value, text); + xmpp_stanza_release(text); + + xmpp_stanza_add_child(field, value); + xmpp_stanza_release(value); + + xmpp_stanza_add_child(x, field); + xmpp_stanza_release(field); + } + + if (start) + { + field = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(field, "field"); + xmpp_stanza_set_attribute(field, "var", "start"); + + value = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(value, "value"); + + text = xmpp_stanza_new(account->context); + char time[256] = {0}; + strftime(time, sizeof(time), "%Y-%m-%dT%H:%M:%SZ", gmtime(start)); + xmpp_stanza_set_text(text, time); + xmpp_stanza_add_child(value, text); + xmpp_stanza_release(text); + + xmpp_stanza_add_child(field, value); + xmpp_stanza_release(value); + + xmpp_stanza_add_child(x, field); + xmpp_stanza_release(field); + } + + if (end) + { + field = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(field, "field"); + xmpp_stanza_set_attribute(field, "var", "end"); + + value = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(value, "value"); + + text = xmpp_stanza_new(account->context); + char time[256] = {0}; + strftime(time, sizeof(time), "%Y-%m-%dT%H:%M:%SZ", gmtime(end)); + xmpp_stanza_set_text(text, time); + xmpp_stanza_add_child(value, text); + xmpp_stanza_release(text); + + xmpp_stanza_add_child(field, value); + xmpp_stanza_release(value); + + xmpp_stanza_add_child(x, field); + xmpp_stanza_release(field); + } + + xmpp_stanza_add_child(query, x); + xmpp_stanza_release(x); + + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + xmpp_send(account->connection, iq); + xmpp_stanza_release(iq); +} diff --git a/channel.h b/channel.h index 4bb4a93..52d799d 100644 --- a/channel.h +++ b/channel.h @@ -13,6 +13,13 @@ enum t_channel_type CHANNEL_TYPE_PM, }; +enum t_channel_transport +{ + CHANNEL_TRANSPORT_PLAINTEXT, + CHANNEL_TRANSPORT_OMEMO, + CHANNEL_TRANSPORT_PGP, +}; + struct t_channel_typing { union { @@ -44,11 +51,18 @@ struct t_channel_topic time_t last_set; }; +struct t_channel_unread +{ + char *id; + char *thread; +}; + struct t_channel { enum t_channel_type type; char *id; char *name; + enum t_channel_transport transport; char *pgp_id; struct t_channel_topic topic; @@ -62,6 +76,7 @@ struct t_channel struct t_hook *typing_hook_timer; struct t_hook *self_typing_hook_timer; struct t_weelist *members_speaking[2]; + struct t_weelist *unreads; struct t_channel_typing *self_typings; struct t_channel_typing *last_self_typing; struct t_channel_typing *typings; @@ -75,6 +90,8 @@ struct t_channel struct t_channel *next_channel; }; +const char *channel__transport_name(enum t_channel_transport transport); + struct t_account *channel__account(struct t_channel *channel); struct t_channel *channel__search(struct t_account *account, @@ -128,6 +145,14 @@ struct t_channel_typing *channel__self_typing_search(struct t_channel *channel, int channel__add_self_typing(struct t_channel *channel, struct t_user *user); +int channel__hotlist_update_cb(const void *pointer, void *data, + const char *signal, const char *type_data, + void *signal_data); + +void channel__unread_free(struct t_channel_unread *unread); + +void channel__unread_free_all(struct t_channel *channel); + void channel__free(struct t_account *account, struct t_channel *channel); @@ -163,10 +188,15 @@ struct t_channel_member *channel__remove_member(struct t_account *account, void channel__send_message(struct t_account *account, struct t_channel *channel, const char *to, const char *body); +void channel__send_reads(struct t_account *account, struct t_channel *channel); + void channel__send_typing(struct t_account *account, struct t_channel *channel, struct t_user *user); void channel__send_paused(struct t_account *account, struct t_channel *channel, struct t_user *user); +void channel__fetch_mam(struct t_account *account, struct t_channel *channel, + time_t *start, time_t *end); + #endif /*WEECHAT_XMPP_CHANNEL_H*/ diff --git a/completion.c b/completion.c index 7a98152..9fb976a 100644 --- a/completion.c +++ b/completion.c @@ -30,7 +30,7 @@ void completion__channel_nicks_add_speakers(struct t_gui_completion *completion, list_size = weechat_list_size(channel->members_speaking[highlight]); for (i = 0; i < list_size; i++) { - member = weechat_list_string ( + member = weechat_list_string( weechat_list_get(channel->members_speaking[highlight], i)); if (member) { diff --git a/connection.c b/connection.c index 34146f0..e3a6c39 100644 --- a/connection.c +++ b/connection.c @@ -87,7 +87,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void struct t_user *user; struct t_channel *channel; xmpp_stanza_t *iq__x_signed, *iq__x_muc_user, *iq__x__item, *iq__c, *iq__status; - const char *from, *from_bare, *from_res, *role = NULL, *affiliation = NULL, *jid = NULL; + const char *from, *from_bare, *from_res, *type, *role = NULL, *affiliation = NULL, *jid = NULL; const char *certificate = NULL, *node = NULL, *ver = NULL; char *clientid = NULL, *status; @@ -96,6 +96,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void return 1; from_bare = xmpp_jid_bare(account->context, from); from_res = xmpp_jid_resource(account->context, from); + type = xmpp_stanza_get_type(stanza); iq__x_signed = xmpp_stanza_get_child_by_name_and_ns( stanza, "x", "jabber:x:signed"); if (iq__x_signed) @@ -130,11 +131,12 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void status = iq__status ? xmpp_stanza_get_text(iq__status) : NULL; channel = channel__search(account, from_bare); - if (!iq__x_muc_user && !channel) + if (weechat_strcasecmp(type, "unavailable") && !iq__x_muc_user && !channel) channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare); - if (certificate) + if (certificate && channel) { - channel->pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); + if (channel->type != CHANNEL_TYPE_MUC) + channel->pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); weechat_printf(channel->buffer, "[PGP]\t%sKey %s from %s", weechat_color("gray"), channel->pgp_id, from); } @@ -145,7 +147,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void channel && weechat_strcasecmp(from_bare, channel->id) == 0 ? from_res : from); - if (!iq__x_muc_user) + if (!iq__x_muc_user && channel) { channel__add_member(account, channel, from, clientid, status); } @@ -171,8 +173,8 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * struct t_account *account = (struct t_account *)userdata; struct t_channel *channel; - xmpp_stanza_t *x, *body, *delay, *topic, *replace, *composing, *sent, *received, *forwarded; - const char *type, *from, *nick, *from_bare, *to, *to_bare, *id, *replace_id, *timestamp; + xmpp_stanza_t *x, *body, *delay, *topic, *replace, *request, *markable, *composing, *sent, *received, *result, *forwarded; + const char *type, *from, *nick, *from_bare, *to, *to_bare, *id, *thread, *replace_id, *timestamp; char *text, *intext, *difftext = NULL, *cleartext = NULL; struct tm time = {0}; time_t date = 0; @@ -237,6 +239,29 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * return connection__message_handler(conn, message, userdata); } + result = xmpp_stanza_get_child_by_name_and_ns( + stanza, "result", "urn:xmpp:mam:2"); + if (result) + { + forwarded = xmpp_stanza_get_child_by_name_and_ns( + result, "forwarded", "urn:xmpp:forward:0"); + if (forwarded != NULL) + { + xmpp_stanza_t *message = xmpp_stanza_get_child_by_name(forwarded, "message"); + if (message) + { + message = xmpp_stanza_copy(message); + delay = xmpp_stanza_get_child_by_name_and_ns( + forwarded, "delay", "urn:xmpp:delay"); + if (delay != NULL) + xmpp_stanza_add_child_ex(message, xmpp_stanza_copy(delay), 0); + int ret = connection__message_handler(conn, message, userdata); + xmpp_stanza_release(message); + return ret; + } + } + } + return 1; } type = xmpp_stanza_get_type(stanza); @@ -247,11 +272,18 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * return 1; from_bare = xmpp_jid_bare(account->context, from); to = xmpp_stanza_get_to(stanza); + if (to == NULL) + to = account_jid(account); to_bare = to ? xmpp_jid_bare(account->context, to) : NULL; id = xmpp_stanza_get_id(stanza); + thread = xmpp_stanza_get_attribute(stanza, "thread"); replace = xmpp_stanza_get_child_by_name_and_ns(stanza, "replace", "urn:xmpp:message-correct:0"); replace_id = replace ? xmpp_stanza_get_id(replace) : NULL; + request = xmpp_stanza_get_child_by_name_and_ns(stanza, "request", + "urn:xmpp:receipts"); + markable = xmpp_stanza_get_child_by_name_and_ns(stanza, "markable", + "urn:xmpp:chat-markers:0"); const char *channel_id = weechat_strcasecmp(account_jid(account), from_bare) == 0 ? to_bare : from_bare; @@ -262,6 +294,59 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * ? CHANNEL_TYPE_MUC : CHANNEL_TYPE_PM, channel_id, channel_id); + if (id && (markable || request)) + { + struct t_channel_unread *unread = malloc(sizeof(struct t_channel_unread)); + unread->id = strdup(id); + unread->thread = thread ? strdup(thread) : NULL; + + xmpp_stanza_t *message = xmpp_message_new(account->context, NULL, + channel->id, NULL); + + if (request) + { + xmpp_stanza_t *message__received = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__received, "received"); + xmpp_stanza_set_ns(message__received, "urn:xmpp:receipts"); + xmpp_stanza_set_id(message__received, unread->id); + + xmpp_stanza_add_child(message, message__received); + xmpp_stanza_release(message__received); + } + + if (markable) + { + xmpp_stanza_t *message__received = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__received, "received"); + xmpp_stanza_set_ns(message__received, "urn:xmpp:chat-markers:0"); + xmpp_stanza_set_id(message__received, unread->id); + + xmpp_stanza_add_child(message, message__received); + xmpp_stanza_release(message__received); + } + + if (unread->thread) + { + xmpp_stanza_t *message__thread = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__thread, "thread"); + + xmpp_stanza_t *message__thread__text = xmpp_stanza_new(account->context); + xmpp_stanza_set_text(message__thread__text, unread->thread); + xmpp_stanza_add_child(message__thread, message__thread__text); + xmpp_stanza_release(message__thread__text); + + xmpp_stanza_add_child(message, message__thread); + xmpp_stanza_release(message__thread); + } + + xmpp_send(account->connection, message); + xmpp_stanza_release(message); + + if (!channel->unreads) + channel->unreads = weechat_list_new(); + weechat_list_add(channel->unreads, unread->id, WEECHAT_LIST_POS_END, unread); + } + x = xmpp_stanza_get_child_by_name_and_ns(stanza, "x", "jabber:x:encrypted"); intext = xmpp_stanza_get_text(body); if (x) @@ -446,8 +531,20 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * weechat_string_dyn_concat(dyn_tags, ",log1", -1); const char *edit = replace ? "* " : ""; // Losing which message was edited, sadly - if (x && text == cleartext) - weechat_printf(channel->buffer, "[~]\t%s%s: PGP", weechat_color("gray"), nick); + if (x && text == cleartext && channel->transport != CHANNEL_TRANSPORT_PGP) + { + channel->transport = CHANNEL_TRANSPORT_PGP; + weechat_printf_date_tags(channel->buffer, date, NULL, "%s%sTransport: %s", + weechat_prefix("network"), weechat_color("gray"), + channel__transport_name(channel->transport)); + } + else if (!x && text == intext && channel->transport != CHANNEL_TRANSPORT_PLAINTEXT) + { + channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; + weechat_printf_date_tags(channel->buffer, date, NULL, "%s%sTransport: %s", + weechat_prefix("network"), weechat_color("gray"), + channel__transport_name(channel->transport)); + } if (channel_id == from_bare && strcmp(to, channel->id) == 0) weechat_printf_date_tags(channel->buffer, date, *dyn_tags, "%s%s\t[to %s]: %s", edit, user__as_prefix_raw(account, nick), @@ -726,7 +823,7 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd { account__free_device_all(account); - struct t_device *dev = malloc(sizeof(dev)); + struct t_account_device *dev = malloc(sizeof(struct t_account_device)); char id[64] = {0}; dev->id = account->omemo->device_id; @@ -751,7 +848,7 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd device_id = xmpp_stanza_get_id(device); - dev = malloc(sizeof(dev)); + dev = malloc(sizeof(struct t_account_device)); dev->id = atoi(device_id); dev->name = strdup(device_id); account__add_device(account, dev); @@ -1032,12 +1129,12 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, uint8_t identity[128] = {0}; if (b64_id && *b64_id) weechat_string_base_decode(64, b64_id, (char*)identity); - struct t_identity id_key = { + struct t_omemo_identity id_key = { .key = identity, .length = 4, }; - omemo__init(&account->omemo, dev_id, b64_id && *b64_id ? &id_key : NULL); + omemo__init(account->buffer, &account->omemo, dev_id, b64_id && *b64_id ? &id_key : NULL); char account_id[64] = {0}; snprintf(account_id, sizeof(account_id), "%d", account->omemo->device_id); diff --git a/input.c b/input.c index a57ad4b..dc9fd6f 100644 --- a/input.c +++ b/input.c @@ -64,6 +64,7 @@ int input__typing(struct t_gui_buffer *buffer) if (account && account->is_connected && channel) { + channel__send_reads(account, channel); channel__send_typing(account, channel, NULL); } diff --git a/omemo.c b/omemo.c index c8e7795..0c10673 100644 --- a/omemo.c +++ b/omemo.c @@ -7,108 +7,472 @@ #include #include #include +#include +#include +#include +#include "plugin.h" +#include "account.h" #include "omemo.h" -/* -char *omemo__signal_init() -{ - signal_protocol_store_context *store_context_p = NULL; - - signal_protocol_session_store session_store = { - .load_session_func = &axc_db_session_load, - .get_sub_device_sessions_func = &axc_db_session_get_sub_device_sessions, - .store_session_func = &axc_db_session_store, - .contains_session_func = &axc_db_session_contains, - .delete_session_func = &axc_db_session_delete, - .delete_all_sessions_func = &axc_db_session_delete_all, - .destroy_func = &axc_db_session_destroy_store_ctx, - .user_data = ctx_p - }; - signal_protocol_pre_key_store pre_key_store = { - .load_pre_key = &axc_db_pre_key_load, - .store_pre_key = &axc_db_pre_key_store, - .contains_pre_key = &axc_db_pre_key_contains, - .remove_pre_key = &axc_db_pre_key_remove, - .destroy_func = &axc_db_pre_key_destroy_ctx, - .user_data = ctx_p - }; - signal_protocol_signed_pre_key_store signed_pre_key_store = { - .load_signed_pre_key = &axc_db_signed_pre_key_load, - .store_signed_pre_key = &axc_db_signed_pre_key_store, - .contains_signed_pre_key = &axc_db_signed_pre_key_contains, - .remove_signed_pre_key = &axc_db_signed_pre_key_remove, - .destroy_func = &axc_db_signed_pre_key_destroy_ctx, - .user_data = ctx_p - }; - signal_protocol_identity_key_store identity_key_store = { - .get_identity_key_pair = &axc_db_identity_get_key_pair, - .get_local_registration_id = &axc_db_identity_get_local_registration_id, - .save_identity = &axc_db_identity_save, - .is_trusted_identity = &axc_db_identity_always_trusted, - .destroy_func = &axc_db_identity_destroy_ctx, - .user_data = ctx_p - }; +const char *OMEMO_ADVICE = "[OMEMO encrypted message (XEP-0384)]"; - if (signal_context_create(&(ctx_p->axolotl_global_context_p), ctx_p)) { - return "failed to create global axolotl context"; - } - - signal_crypto_provider crypto_provider = { - .random_func = random_bytes, - .hmac_sha256_init_func = hmac_sha256_init, - .hmac_sha256_update_func = hmac_sha256_update, - .hmac_sha256_final_func = hmac_sha256_final, - .sha512_digest_init_func = sha512_digest_init, - .sha512_digest_update_func = sha512_digest_update, - .sha512_digest_final_func = sha512_digest_final, - .encrypt_func = aes_encrypt, - .decrypt_func = aes_decrypt, - .user_data = ctx_p - }; - if (signal_context_set_crypto_provider(ctx_p->axolotl_global_context_p, &crypto_provider)) { - return "failed to set crypto provider"; +signal_type_base* signal_type_ref_vapi(void* instance) { + if (!(instance != NULL)) + return NULL; + signal_type_ref(instance); + return instance; +} + +signal_type_base* signal_type_unref_vapi(void* instance) { + if (!(instance != NULL)) + return NULL; + signal_type_unref(instance); + return NULL; +} + +void signal_protocol_address_free(signal_protocol_address* ptr) { + if (!(ptr != NULL)) + return; + if (ptr->name) { + free((void*)ptr->name); + } + return free(ptr); +} + +void signal_protocol_address_set_name(signal_protocol_address* self, const char* name) { + if (!(self != NULL)) + return; + if (!(name != NULL)) + return; + char* n = malloc(strlen(name)+1); + memcpy(n, name, strlen(name)); + n[strlen(name)] = 0; + if (self->name) { + free((void*)self->name); + } + self->name = n; + self->name_len = strlen(n); +} + +char* signal_protocol_address_get_name(signal_protocol_address* self) { + if (!(self != NULL)) + return NULL; + if (!(self->name != NULL)) + return 0; + char* res = malloc(sizeof(char) * (self->name_len + 1)); + memcpy(res, self->name, self->name_len); + res[self->name_len] = 0; + return res; +} + +int32_t signal_protocol_address_get_device_id(signal_protocol_address* self) { + if (!(self != NULL)) + return -1; + return self->device_id; +} + +void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id) { + if (!(self != NULL)) + return; + self->device_id = device_id; +} + +signal_protocol_address* signal_protocol_address_new(const char* name, int32_t device_id) { + if (!(name != NULL)) + return NULL; + signal_protocol_address* address = malloc(sizeof(signal_protocol_address)); + address->device_id = -1; + address->name = NULL; + signal_protocol_address_set_name(address, name); + signal_protocol_address_set_device_id(address, device_id); + return address; +} + +int signal_randomize(uint8_t *data, size_t len) { + gcry_randomize(data, len, GCRY_STRONG_RANDOM); + return SG_SUCCESS; +} + +int signal_random_generator(uint8_t *data, size_t len, void *user_data) { + (void) user_data; + + gcry_randomize(data, len, GCRY_STRONG_RANDOM); + return SG_SUCCESS; +} + +int signal_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) { + (void) user_data; + + gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); + if (!ctx) return SG_ERR_NOMEM; + + if (gcry_mac_open(ctx, GCRY_MAC_HMAC_SHA256, 0, 0)) { + free(ctx); + return SG_ERR_UNKNOWN; + } + + if (gcry_mac_setkey(*ctx, key, key_len)) { + free(ctx); + return SG_ERR_UNKNOWN; + } + + *hmac_context = ctx; + + return SG_SUCCESS; +} + +int signal_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) { + (void) user_data; + + gcry_mac_hd_t* ctx = hmac_context; + + if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN; + + return SG_SUCCESS; +} + +int signal_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) { + (void) user_data; + + size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256); + uint8_t md[len]; + gcry_mac_hd_t* ctx = hmac_context; + + if (gcry_mac_read(*ctx, md, &len)) return SG_ERR_UNKNOWN; + + signal_buffer *output_buffer = signal_buffer_create(md, len); + if (!output_buffer) return SG_ERR_NOMEM; + + *output = output_buffer; + + return SG_SUCCESS; +} + +void signal_hmac_sha256_cleanup(void *hmac_context, void *user_data) { + (void) user_data; + + gcry_mac_hd_t* ctx = hmac_context; + if (ctx) { + gcry_mac_close(*ctx); + free(ctx); + } +} + +int signal_sha512_digest_init(void **digest_context, void *user_data) { + (void) user_data; + + gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); + if (!ctx) return SG_ERR_NOMEM; + + if (gcry_md_open(ctx, GCRY_MD_SHA512, 0)) { + free(ctx); + return SG_ERR_UNKNOWN; + } + + *digest_context = ctx; + + return SG_SUCCESS; +} + +int signal_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) { + (void) user_data; + + gcry_md_hd_t* ctx = digest_context; + + gcry_md_write(*ctx, data, data_len); + + return SG_SUCCESS; +} + +int signal_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) { + (void) user_data; + + size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512); + gcry_md_hd_t* ctx = digest_context; + + uint8_t* md = gcry_md_read(*ctx, GCRY_MD_SHA512); + if (!md) return SG_ERR_UNKNOWN; + + gcry_md_reset(*ctx); + + signal_buffer *output_buffer = signal_buffer_create(md, len); + free(md); + if (!output_buffer) return SG_ERR_NOMEM; + + *output = output_buffer; + + return SG_SUCCESS; +} + +void signal_sha512_digest_cleanup(void *digest_context, void *user_data) { + (void) user_data; + + gcry_md_hd_t* ctx = digest_context; + if (ctx) { + gcry_md_close(*ctx); + free(ctx); + } +} + +int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { + switch (key_len) { + case 16: + *algo = GCRY_CIPHER_AES128; + break; + case 24: + *algo = GCRY_CIPHER_AES192; + break; + case 32: + *algo = GCRY_CIPHER_AES256; + break; + default: + return SG_ERR_UNKNOWN; + } + switch (cipher) { + case SG_CIPHER_AES_CBC_PKCS5: + *mode = GCRY_CIPHER_MODE_CBC; + break; + case SG_CIPHER_AES_CTR_NOPADDING: + *mode = GCRY_CIPHER_MODE_CTR; + break; + default: + return SG_ERR_UNKNOWN; + } + return SG_SUCCESS; +} + +int signal_encrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *plaintext, size_t plaintext_len, + void *user_data) { + (void) user_data; + + int algo, mode, error_code = SG_ERR_UNKNOWN; + if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; + + gcry_cipher_hd_t ctx = {0}; + + if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; + + signal_buffer* padded = 0; + signal_buffer* out_buf = 0; + goto no_error; +error: + gcry_cipher_close(ctx); + if (padded != 0) { + signal_buffer_bzero_free(padded); + } + if (out_buf != 0) { + signal_buffer_free(out_buf); } + return error_code; +no_error: - if (signal_context_set_locking_functions(ctx_p->axolotl_global_context_p, recursive_mutex_lock, recursive_mutex_unlock)) { - return "failed to set locking functions"; + if (gcry_cipher_setkey(ctx, key, key_len)) goto error; + + uint8_t tag_len = 0, pad_len = 0; + switch (cipher) { + case SG_CIPHER_AES_CBC_PKCS5: + if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; + pad_len = 16 - (plaintext_len % 16); + if (pad_len == 0) pad_len = 16; + break; + case SG_CIPHER_AES_CTR_NOPADDING: + if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; + break; + default: + return SG_ERR_UNKNOWN; } - if (signal_protocol_store_context_create(&store_context_p, ctx_p->axolotl_global_context_p)) { - return "failed to create store context"; + size_t padded_len = plaintext_len + pad_len; + padded = signal_buffer_alloc(padded_len); + if (padded == 0) { + error_code = SG_ERR_NOMEM; + goto error; } - if (signal_protocol_store_context_set_session_store(store_context_p, &session_store)) { - return "failed to create session store"; + memset(signal_buffer_data(padded) + plaintext_len, pad_len, pad_len); + memcpy(signal_buffer_data(padded), plaintext, plaintext_len); + + out_buf = signal_buffer_alloc(padded_len + tag_len); + if (out_buf == 0) { + error_code = SG_ERR_NOMEM; + goto error; } - if (signal_protocol_store_context_set_pre_key_store(store_context_p, &pre_key_store)) { - return "failed to set pre key store"; + if (gcry_cipher_encrypt(ctx, signal_buffer_data(out_buf), padded_len, signal_buffer_data(padded), padded_len)) goto error; + + if (tag_len > 0) { + if (gcry_cipher_gettag(ctx, signal_buffer_data(out_buf) + padded_len, tag_len)) goto error; } - if (signal_protocol_store_context_set_signed_pre_key_store(store_context_p, &signed_pre_key_store)) { - return "failed to set signed pre key store"; + *output = out_buf; + out_buf = 0; + + signal_buffer_bzero_free(padded); + padded = 0; + + gcry_cipher_close(ctx); + return SG_SUCCESS; +} + +int signal_decrypt(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *ciphertext, size_t ciphertext_len, + void *user_data) { + (void) user_data; + + int algo, mode, error_code = SG_ERR_UNKNOWN; + *output = 0; + if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; + if (ciphertext_len == 0) return SG_ERR_INVAL; + + gcry_cipher_hd_t ctx = {0}; + + if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; + + signal_buffer* out_buf = 0; + goto no_error; +error: + gcry_cipher_close(ctx); + if (out_buf != 0) { + signal_buffer_bzero_free(out_buf); } + return error_code; +no_error: + + if (gcry_cipher_setkey(ctx, key, key_len)) goto error; - if (signal_protocol_store_context_set_identity_key_store(store_context_p, &identity_key_store)) { - return "failed to set identity key store"; + uint8_t tag_len = 0, pkcs_pad = 0; + switch (cipher) { + case SG_CIPHER_AES_CBC_PKCS5: + if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; + pkcs_pad = 1; + break; + case SG_CIPHER_AES_CTR_NOPADDING: + if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; + break; + default: + goto error; } - ctx_p->axolotl_store_context_p = store_context_p; + size_t padded_len = ciphertext_len - tag_len; + out_buf = signal_buffer_alloc(padded_len); + if (out_buf == 0) { + error_code = SG_ERR_NOMEM; + goto error; + } - return NULL; + if (gcry_cipher_decrypt(ctx, signal_buffer_data(out_buf), signal_buffer_len(out_buf), ciphertext, padded_len)) goto error; + + if (tag_len > 0) { + if (gcry_cipher_checktag(ctx, ciphertext + padded_len, tag_len)) goto error; + } + + if (pkcs_pad) { + uint8_t pad_len = signal_buffer_data(out_buf)[padded_len - 1]; + if (pad_len > 16 || pad_len > padded_len) goto error; + *output = signal_buffer_create(signal_buffer_data(out_buf), padded_len - pad_len); + signal_buffer_bzero_free(out_buf); + out_buf = 0; + } else { + *output = out_buf; + out_buf = 0; + } + + gcry_cipher_close(ctx); + return SG_SUCCESS; } -*/ -void omemo__init(struct t_omemo **omemo, uint32_t device, - struct t_identity *const identity) +void lock_function(void *user_data) { - struct t_omemo *new_omemo; + (void) user_data; +} + +void unlock_function(void *user_data) +{ + (void) user_data; +} + +void omemo__log_emit_weechat(int level, const char *message, size_t len, void *user_data) +{ + struct t_gui_buffer *buffer = (struct t_gui_buffer*)user_data; + + static const char *log_level_name[5] = {"error", "warn", "notice", "info", "debug"}; + + const char *tags = level < SG_LOG_DEBUG ? "no_log" : NULL; + + weechat_printf_date_tags( + buffer, 0, tags, + _("%somemo (%s): %.*s"), + weechat_prefix("network"), + log_level_name[level], len, message); +} + +int omemo__signal_init(struct t_gui_buffer *buffer, struct t_omemo *omemo) +{ + signal_context *global_context; + + gcry_check_version(NULL); + + signal_context_create(&global_context, buffer); + signal_context_set_log_function(global_context, &omemo__log_emit_weechat); + + struct signal_crypto_provider provider = { + .random_func = &signal_random_generator, + .hmac_sha256_init_func = &signal_hmac_sha256_init, + .hmac_sha256_update_func = &signal_hmac_sha256_update, + .hmac_sha256_final_func = &signal_hmac_sha256_final, + .hmac_sha256_cleanup_func = &signal_hmac_sha256_cleanup, + .sha512_digest_init_func = &signal_sha512_digest_init, + .sha512_digest_update_func = &signal_sha512_digest_update, + .sha512_digest_final_func = &signal_sha512_digest_final, + .sha512_digest_cleanup_func = &signal_sha512_digest_cleanup, + .encrypt_func = &signal_encrypt, + .decrypt_func = &signal_decrypt, + .user_data = buffer, + }; + + signal_context_set_crypto_provider(global_context, &provider); + signal_context_set_locking_functions(global_context, &lock_function, &unlock_function); + + ratchet_identity_key_pair *identity_key_pair; + uint32_t registration_id; + signal_protocol_key_helper_pre_key_list_node *pre_keys_head; + session_signed_pre_key *signed_pre_key; + int start_id = 0; + time_t timestamp = time(NULL); + + signal_protocol_key_helper_generate_identity_key_pair(&identity_key_pair, global_context); + signal_protocol_key_helper_generate_registration_id(®istration_id, 0, global_context); + signal_protocol_key_helper_generate_pre_keys(&pre_keys_head, start_id, 100, global_context); + signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, identity_key_pair, 5, timestamp, global_context); + + /* Store identity_key_pair somewhere durable and safe. */ + /* Store registration_id somewhere durable and safe. */ - srandom(time(NULL)); + /* Store pre keys in the pre key store. */ + /* Store signed pre key in the signed pre key store. */ + + omemo->context = global_context; + + return SG_SUCCESS; +} + +void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, + uint32_t device, struct t_omemo_identity *const identity) +{ + struct t_omemo *new_omemo; new_omemo = calloc(1, sizeof(**omemo)); + omemo__signal_init(buffer, new_omemo); + new_omemo->identity = malloc(sizeof(*identity)); if (identity) { @@ -120,7 +484,7 @@ void omemo__init(struct t_omemo **omemo, uint32_t device, else { new_omemo->identity->length = 4; - new_omemo->identity->key = calloc(identity->length, sizeof(*identity->key)); + new_omemo->identity->key = calloc(new_omemo->identity->length, sizeof(*new_omemo->identity->key)); new_omemo->identity->key[0] = random(); new_omemo->identity->key[1] = random(); @@ -137,6 +501,8 @@ void omemo__free(struct t_omemo *omemo) { if (omemo) { + if (omemo->context) + signal_context_destroy(omemo->context); if (omemo->identity->key) free(omemo->identity->key); if (omemo->identity) diff --git a/omemo.h b/omemo.h index 2ce48fa..96d7885 100644 --- a/omemo.h +++ b/omemo.h @@ -7,7 +7,7 @@ extern const char *OMEMO_ADVICE; -struct t_identity +struct t_omemo_identity { uint8_t *key; size_t length; @@ -15,18 +15,19 @@ struct t_identity struct t_omemo { + struct signal_context *context; //omemo_crypto_provider provider; //axc_context *context; //axc_bundle *a_bundle; //omemo_bundle *o_bundle; - struct t_identity *identity; + struct t_omemo_identity *identity; uint32_t device_id; }; -void omemo__init(struct t_omemo **omemo, uint32_t device, - struct t_identity *identity); +void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, + uint32_t device, struct t_omemo_identity *identity); void omemo__free(struct t_omemo *omemo); diff --git a/pgp.c b/pgp.c index dc1c65c..a879e81 100644 --- a/pgp.c +++ b/pgp.c @@ -240,6 +240,7 @@ char *pgp__verify(struct t_gui_buffer *buffer, struct t_pgp *pgp, const char *ce for (size_t i = 0; i < sigcount; i++) { rnp_op_verify_signature_t sig = NULL; rnp_key_handle_t key = NULL; + rnp_signature_handle_t signature = NULL; char * keyid = NULL; if ((ret = rnp_op_verify_get_signature_at(verify, i, &sig)) != RNP_SUCCESS) { @@ -248,28 +249,36 @@ char *pgp__verify(struct t_gui_buffer *buffer, struct t_pgp *pgp, const char *ce goto verify_finish; } - if ((ret = rnp_op_verify_signature_get_key(sig, &key)) != RNP_SUCCESS) { - const char *reason = rnp_result_to_string(ret); - weechat_printf(buffer, "[PGP]\tfailed to get signature's %d key: %s\n", (int)i, reason); - goto verify_finish; - } + if ((ret = rnp_op_verify_signature_get_key(sig, &key)) == RNP_SUCCESS) { + if ((ret = rnp_key_get_keyid(key, &keyid)) != RNP_SUCCESS) { + const char *reason = rnp_result_to_string(ret); + weechat_printf(buffer, "[PGP]\tfailed to get key id %d: %s\n", (int)i, reason); + rnp_key_handle_destroy(key); + goto verify_finish; + } - if ((ret = rnp_key_get_keyid(key, &keyid)) != RNP_SUCCESS) { - const char *reason = rnp_result_to_string(ret); - weechat_printf(buffer, "[PGP]\tfailed to get key id %d: %s\n", (int)i, reason); - rnp_key_handle_destroy(key); - goto verify_finish; - } + if ((ret = rnp_key_get_signature_at(key, 0, &signature)) == RNP_SUCCESS) { + if ((ret = rnp_signature_get_keyid(signature, &keyid)) != RNP_SUCCESS) { + const char *reason = rnp_result_to_string(ret); + weechat_printf(buffer, "[PGP]\tfailed to get key id: %s\n", reason); + rnp_key_handle_destroy(key); + goto verify_finish; + } + rnp_signature_handle_destroy(signature); + } + } else { + if ((ret = rnp_op_verify_signature_get_handle(sig, &signature)) != RNP_SUCCESS) { + const char *reason = rnp_result_to_string(ret); + weechat_printf(buffer, "[PGP]\tfailed to get signature's %d handle: %s\n", (int)i, reason); + goto verify_finish; + } - rnp_signature_handle_t signature = NULL; - if ((ret = rnp_key_get_signature_at(key, 0, &signature)) == RNP_SUCCESS) { if ((ret = rnp_signature_get_keyid(signature, &keyid)) != RNP_SUCCESS) { const char *reason = rnp_result_to_string(ret); weechat_printf(buffer, "[PGP]\tfailed to get key id: %s\n", reason); rnp_key_handle_destroy(key); goto verify_finish; } - rnp_signature_handle_destroy(signature); } result = strdup(keyid); diff --git a/user.c b/user.c index 6698c50..50abe1c 100644 --- a/user.c +++ b/user.c @@ -95,7 +95,7 @@ void user__nicklist_add(struct t_account *account, { struct t_gui_nick_group *ptr_group; struct t_gui_buffer *ptr_buffer; - char *name = user->profile.display_name; + char *name = channel ? user->profile.display_name : user->id; if (channel && weechat_strcasecmp(xmpp_jid_bare(account->context, name), channel->id) == 0) name = xmpp_jid_resource(account->context, name); diff --git a/util.c b/util.c index 15972a8..382ee05 100644 --- a/util.c +++ b/util.c @@ -2,7 +2,9 @@ // License, version 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#include #include +#include #include #include "plugin.h" @@ -12,3 +14,36 @@ int char_cmp(const void *p1, const void *p2) { return *(const char *)p1 == *(const char *)p2; } + +char *exec(const char *command) +{ + // use hook_process instead! + char buffer[128]; + char **result = weechat_string_dyn_alloc(256); + + // Open pipe to file + FILE* pipe = popen(command, "r"); + if (!pipe) { + return "popen failed!"; + } + + // read till end of process: + while (!feof(pipe)) { + + // use buffer to read and add to result + if (fgets(buffer, 128, pipe) != NULL) + weechat_string_dyn_concat(result, buffer, -1); + } + + pclose(pipe); + weechat_string_dyn_free(result, 0); + return *result; +} + +char *stanza_xml(xmpp_stanza_t *stanza) +{ + char *result; + size_t len; + xmpp_stanza_to_text(stanza, &result, &len); + return result; +} diff --git a/util.h b/util.h index 2226299..0c23aa9 100644 --- a/util.h +++ b/util.h @@ -7,4 +7,8 @@ int char_cmp(const void *p1, const void *p2); +char *exec(const char *command); + +char *stanza_xml(struct _xmpp_stanza_t *stanza); + #endif /*WEECHAT_XMPP_UTIL_H*/