see readme

v1
Tony Olagbaiye 3 years ago
parent 6d6d155574
commit b2f38c4901
No known key found for this signature in database
GPG Key ID: 9E2FF3BDEBDFC910

@ -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

@ -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) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
+-------------------------------------------------------------------------+------------------------+---------------+------+-------+----------+-------+--------+-------+

@ -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

@ -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;

@ -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);

@ -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);
}

@ -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*/

@ -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)
{

@ -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);

@ -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);
}

@ -7,108 +7,472 @@
#include <time.h>
#include <gcrypt.h>
#include <signal_protocol.h>
#include <key_helper.h>
#include <strophe.h>
#include <weechat/weechat-plugin.h>
#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(&registration_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)

@ -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);

37
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);

@ -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);

@ -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 <stdio.h>
#include <stdlib.h>
#include <strophe.h>
#include <weechat/weechat-plugin.h>
#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;
}

@ -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*/

Loading…
Cancel
Save