1 /*
2     dn.cpp
3 
4     This file is part of libkleopatra, the KDE keymanagement library
5     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6 
7     DN parsing:
8     SPDX-FileCopyrightText: 2002 g 10 Code GmbH
9     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
10 
11     SPDX-License-Identifier: GPL-2.0-or-later
12 */
13 
14 #include "dn.h"
15 
16 #include "oidmap.h"
17 
18 #include "ui/dnattributeorderconfigwidget.h"
19 
20 #include <KConfig>
21 #include <KLocalizedString>
22 
23 
24 #include <iostream>
25 #include <iterator>
26 #include <algorithm>
27 #include <map>
28 
29 #include <string.h>
30 #include <ctype.h>
31 #include <stdlib.h>
32 #include <KConfigGroup>
33 #include <KSharedConfig>
34 
35 #ifdef _MSC_VER
36 #define strcasecmp _stricmp
37 #endif
38 
39 class Kleo::DN::Private
40 {
41 public:
Private()42     Private() : mRefCount(0) {}
Private(const Private & other)43     Private(const Private &other)
44         : attributes(other.attributes),
45           reorderedAttributes(other.reorderedAttributes),
46           mRefCount(0)
47     {
48 
49     }
50 
ref()51     int ref()
52     {
53         return ++mRefCount;
54     }
55 
unref()56     int unref()
57     {
58         if (--mRefCount <= 0) {
59             delete this;
60             return 0;
61         } else {
62             return mRefCount;
63         }
64     }
65 
refCount() const66     int refCount() const
67     {
68         return mRefCount;
69     }
70 
71     DN::Attribute::List attributes;
72     DN::Attribute::List reorderedAttributes;
73 private:
74     int mRefCount;
75 };
76 
77 namespace
78 {
79 struct DnPair {
80     char *key;
81     char *value;
82 };
83 }
84 
85 // copied from CryptPlug and adapted to work on DN::Attribute::List:
86 
87 #define digitp(p)   (*(p) >= '0' && *(p) <= '9')
88 #define hexdigitp(a) (digitp (a)                     \
89                       || (*(a) >= 'A' && *(a) <= 'F')  \
90                       || (*(a) >= 'a' && *(a) <= 'f'))
91 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
92                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
93 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
94 
95 static char *
trim_trailing_spaces(char * string)96 trim_trailing_spaces(char *string)
97 {
98     char *p;
99     char *mark;
100 
101     for (mark = nullptr, p = string; *p; p++) {
102         if (isspace(*p)) {
103             if (!mark) {
104                 mark = p;
105             }
106         } else {
107             mark = nullptr;
108         }
109     }
110     if (mark) {
111         *mark = '\0';
112     }
113 
114     return string;
115 }
116 
117 /* Parse a DN and return an array-ized one.  This is not a validating
118    parser and it does not support any old-stylish syntax; gpgme is
119    expected to return only rfc2253 compatible strings. */
120 static const unsigned char *
parse_dn_part(DnPair * array,const unsigned char * string)121 parse_dn_part(DnPair *array, const unsigned char *string)
122 {
123     const unsigned char *s;
124     const unsigned char *s1;
125     size_t n;
126     char *p;
127 
128     /* parse attributeType */
129     for (s = string + 1; *s && *s != '='; s++) {
130         ;
131     }
132     if (!*s) {
133         return nullptr;    /* error */
134     }
135     n = s - string;
136     if (!n) {
137         return nullptr;    /* empty key */
138     }
139     p = (char *)malloc(n + 1);
140 
141     memcpy(p, string, n);
142     p[n] = 0;
143     trim_trailing_spaces((char *)p);
144     // map OIDs to their names:
145     for (unsigned int i = 0; i < numOidMaps; ++i) {
146         if (!strcasecmp((char *)p, oidmap[i].oid)) {
147             free(p);
148             p = strdup(oidmap[i].name);
149             break;
150         }
151     }
152     array->key = p;
153     string = s + 1;
154 
155     if (*string == '#') {
156         /* hexstring */
157         string++;
158         for (s = string; hexdigitp(s); s++) {
159             s++;
160         }
161         n = s - string;
162         if (!n || (n & 1)) {
163             return nullptr;    /* empty or odd number of digits */
164         }
165         n /= 2;
166         array->value = p = (char *)malloc(n + 1);
167 
168         for (s1 = string; n; s1 += 2, n--) {
169             *p++ = xtoi_2(s1);
170         }
171         *p = 0;
172     } else {
173         /* regular v3 quoted string */
174         for (n = 0, s = string; *s; s++) {
175             if (*s == '\\') {
176                 /* pair */
177                 s++;
178                 if (*s == ',' || *s == '=' || *s == '+'
179                         || *s == '<' || *s == '>' || *s == '#' || *s == ';'
180                         || *s == '\\' || *s == '\"' || *s == ' ') {
181                     n++;
182                 } else if (hexdigitp(s) && hexdigitp(s + 1)) {
183                     s++;
184                     n++;
185                 } else {
186                     return nullptr;    /* invalid escape sequence */
187                 }
188             } else if (*s == '\"') {
189                 return nullptr;    /* invalid encoding */
190             } else if (*s == ',' || *s == '=' || *s == '+'
191                        || *s == '<' || *s == '>' || *s == '#' || *s == ';') {
192                 break;
193             } else {
194                 n++;
195             }
196         }
197 
198         array->value = p = (char *)malloc(n + 1);
199 
200         for (s = string; n; s++, n--) {
201             if (*s == '\\') {
202                 s++;
203                 if (hexdigitp(s)) {
204                     *p++ = xtoi_2(s);
205                     s++;
206                 } else {
207                     *p++ = *s;
208                 }
209             } else {
210                 *p++ = *s;
211             }
212         }
213         *p = 0;
214     }
215     return s;
216 }
217 
218 /* Parse a DN and return an array-ized one.  This is not a validating
219    parser and it does not support any old-stylish syntax; gpgme is
220    expected to return only rfc2253 compatible strings. */
221 static Kleo::DN::Attribute::List
parse_dn(const unsigned char * string)222 parse_dn(const unsigned char *string)
223 {
224     if (!string) {
225         return QVector<Kleo::DN::Attribute>();
226     }
227 
228     QVector<Kleo::DN::Attribute> result;
229     while (*string) {
230         while (*string == ' ') {
231             string++;
232         }
233         if (!*string) {
234             break;    /* ready */
235         }
236 
237         DnPair pair = { nullptr, nullptr };
238         string = parse_dn_part(&pair, string);
239         if (!string) {
240             goto failure;
241         }
242         if (pair.key && pair.value) {
243             result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key),
244                                                  QString::fromUtf8(pair.value)));
245         }
246         free(pair.key);
247         free(pair.value);
248 
249         while (*string == ' ') {
250             string++;
251         }
252         if (*string && *string != ',' && *string != ';' && *string != '+') {
253             goto failure;    /* invalid delimiter */
254         }
255         if (*string) {
256             string++;
257         }
258     }
259     return result;
260 
261 failure:
262     return QVector<Kleo::DN::Attribute>();
263 }
264 
265 static QVector<Kleo::DN::Attribute>
parse_dn(const QString & dn)266 parse_dn(const QString &dn)
267 {
268     return parse_dn((const unsigned char *)dn.toUtf8().data());
269 }
270 
dn_escape(const QString & s)271 static QString dn_escape(const QString &s)
272 {
273     QString result;
274     for (int i = 0, end = s.length(); i != end; ++i) {
275         const QChar ch = s[i];
276         switch (ch.unicode()) {
277         case ',':
278         case '+':
279         case '"':
280         case '\\':
281         case '<':
282         case '>':
283         case ';':
284             result += QLatin1Char('\\');
285             // fall through
286             Q_FALLTHROUGH();
287         default:
288             result += ch;
289         }
290     }
291     return result;
292 }
293 
294 static QString
serialise(const QVector<Kleo::DN::Attribute> & dn,const QString & sep)295 serialise(const QVector<Kleo::DN::Attribute> &dn, const QString &sep)
296 {
297     QStringList result;
298     for (QVector<Kleo::DN::Attribute>::const_iterator it = dn.begin(); it != dn.end(); ++it) {
299         if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) {
300             result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed()));
301         }
302     }
303     return result.join(sep);
304 }
305 
306 static Kleo::DN::Attribute::List
reorder_dn(const Kleo::DN::Attribute::List & dn)307 reorder_dn(const Kleo::DN::Attribute::List &dn)
308 {
309     const QStringList &attrOrder = Kleo::DNAttributeMapper::instance()->attributeOrder();
310 
311     Kleo::DN::Attribute::List unknownEntries;
312     Kleo::DN::Attribute::List result;
313     unknownEntries.reserve(dn.size());
314     result.reserve(dn.size());
315 
316     // find all unknown entries in their order of appearance
317     for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) {
318         if (!attrOrder.contains((*it).name())) {
319             unknownEntries.push_back(*it);
320         }
321     }
322 
323     // process the known attrs in the desired order
324     for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) {
325         if (*oit == QLatin1String("_X_")) {
326             // insert the unknown attrs
327             std::copy(unknownEntries.begin(), unknownEntries.end(),
328                       std::back_inserter(result));
329             unknownEntries.clear(); // don't produce dup's
330         } else {
331             for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) {
332                 if ((*dnit).name() == *oit) {
333                     result.push_back(*dnit);
334                 }
335             }
336         }
337     }
338 
339     return result;
340 }
341 
342 //
343 //
344 // class DN
345 //
346 //
347 
DN()348 Kleo::DN::DN()
349 {
350     d = new Private();
351     d->ref();
352 }
353 
DN(const QString & dn)354 Kleo::DN::DN(const QString &dn)
355 {
356     d = new Private();
357     d->ref();
358     d->attributes = parse_dn(dn);
359 }
360 
DN(const char * utf8DN)361 Kleo::DN::DN(const char *utf8DN)
362 {
363     d = new Private();
364     d->ref();
365     if (utf8DN) {
366         d->attributes = parse_dn((const unsigned char *)utf8DN);
367     }
368 }
369 
DN(const DN & other)370 Kleo::DN::DN(const DN &other)
371     : d(other.d)
372 {
373     if (d) {
374         d->ref();
375     }
376 }
377 
~DN()378 Kleo::DN::~DN()
379 {
380     if (d) {
381         d->unref();
382     }
383 }
384 
operator =(const DN & that)385 const Kleo::DN &Kleo::DN::operator=(const DN &that)
386 {
387     if (this->d == that.d) {
388         return *this;
389     }
390 
391     if (that.d) {
392         that.d->ref();
393     }
394     if (this->d) {
395         this->d->unref();
396     }
397 
398     this->d = that.d;
399 
400     return *this;
401 }
402 
prettyDN() const403 QString Kleo::DN::prettyDN() const
404 {
405     if (!d) {
406         return QString();
407     }
408     if (d->reorderedAttributes.empty()) {
409         d->reorderedAttributes = reorder_dn(d->attributes);
410     }
411     return serialise(d->reorderedAttributes, QStringLiteral(","));
412 }
413 
dn() const414 QString Kleo::DN::dn() const
415 {
416     return d ? serialise(d->attributes, QStringLiteral(",")) : QString();
417 }
418 
dn(const QString & sep) const419 QString Kleo::DN::dn(const QString &sep) const
420 {
421     return d ? serialise(d->attributes, sep) : QString();
422 }
423 
424 // static
escape(const QString & value)425 QString Kleo::DN::escape(const QString &value)
426 {
427     return dn_escape(value);
428 }
429 
detach()430 void Kleo::DN::detach()
431 {
432     if (!d) {
433         d = new Kleo::DN::Private();
434         d->ref();
435     } else if (d->refCount() > 1) {
436         Kleo::DN::Private *d_save = d;
437         d = new Kleo::DN::Private(*d);
438         d->ref();
439         d_save->unref();
440     }
441 }
442 
append(const Attribute & attr)443 void Kleo::DN::append(const Attribute &attr)
444 {
445     detach();
446     d->attributes.push_back(attr);
447     d->reorderedAttributes.clear();
448 }
449 
operator [](const QString & attr) const450 QString Kleo::DN::operator[](const QString &attr) const
451 {
452     if (!d) {
453         return QString();
454     }
455     const QString attrUpper = attr.toUpper();
456     for (QVector<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) {
457         if ((*it).name() == attrUpper) {
458             return (*it).value();
459         }
460     }
461     return QString();
462 }
463 
464 static QVector<Kleo::DN::Attribute> empty;
465 
begin() const466 Kleo::DN::const_iterator Kleo::DN::begin() const
467 {
468     return d ? d->attributes.constBegin() : empty.constBegin();
469 }
470 
end() const471 Kleo::DN::const_iterator Kleo::DN::end() const
472 {
473     return d ? d->attributes.constEnd() : empty.constEnd();
474 }
475 
476 /////////////////////
477 
478 namespace
479 {
480 struct ltstr {
operator ()__anon8e268ce90211::ltstr481     bool operator()(const char *s1, const char *s2) const
482     {
483         return qstrcmp(s1, s2) < 0;
484     }
485 };
486 }
487 
488 static const QStringList defaultOrder = {
489     QStringLiteral("CN"),
490     QStringLiteral("L"),
491     QStringLiteral("_X_"),
492     QStringLiteral("OU"),
493     QStringLiteral("O"),
494     QStringLiteral("C"),
495 };
496 
497 static std::pair<const char *, const char *> const attributeLabels[] = {
498 #define MAKE_PAIR(x,y) std::pair<const char*,const char*>( x, y )
499     MAKE_PAIR("CN", I18N_NOOP("Common name")),
500     MAKE_PAIR("SN", I18N_NOOP("Surname")),
501     MAKE_PAIR("GN", I18N_NOOP("Given name")),
502     MAKE_PAIR("L",  I18N_NOOP("Location")),
503     MAKE_PAIR("T",  I18N_NOOP("Title")),
504     MAKE_PAIR("OU", I18N_NOOP("Organizational unit")),
505     MAKE_PAIR("O",  I18N_NOOP("Organization")),
506     MAKE_PAIR("PC", I18N_NOOP("Postal code")),
507     MAKE_PAIR("C",  I18N_NOOP("Country code")),
508     MAKE_PAIR("SP", I18N_NOOP("State or province")),
509     MAKE_PAIR("DC", I18N_NOOP("Domain component")),
510     MAKE_PAIR("BC", I18N_NOOP("Business category")),
511     MAKE_PAIR("EMAIL", I18N_NOOP("Email address")),
512     MAKE_PAIR("MAIL", I18N_NOOP("Mail address")),
513     MAKE_PAIR("MOBILE", I18N_NOOP("Mobile phone number")),
514     MAKE_PAIR("TEL", I18N_NOOP("Telephone number")),
515     MAKE_PAIR("FAX", I18N_NOOP("Fax number")),
516     MAKE_PAIR("STREET", I18N_NOOP("Street address")),
517     MAKE_PAIR("UID", I18N_NOOP("Unique ID"))
518 #undef MAKE_PAIR
519 };
520 static const unsigned int numAttributeLabels = sizeof attributeLabels / sizeof * attributeLabels;
521 
522 class Kleo::DNAttributeMapper::Private
523 {
524 public:
525     Private();
526     std::map<const char *, const char *, ltstr> map;
527     QStringList attributeOrder;
528 };
529 
Private()530 Kleo::DNAttributeMapper::Private::Private()
531     : map(attributeLabels, attributeLabels + numAttributeLabels) {}
532 
DNAttributeMapper()533 Kleo::DNAttributeMapper::DNAttributeMapper()
534 {
535     d = new Private();
536     const KConfigGroup config(KSharedConfig::openConfig(), "DN");
537     d->attributeOrder = config.readEntry("AttributeOrder", defaultOrder);
538     mSelf = this;
539 }
540 
~DNAttributeMapper()541 Kleo::DNAttributeMapper::~DNAttributeMapper()
542 {
543     mSelf = nullptr;
544     delete d; d = nullptr;
545 }
546 
547 Kleo::DNAttributeMapper *Kleo::DNAttributeMapper::mSelf = nullptr;
548 
instance()549 const Kleo::DNAttributeMapper *Kleo::DNAttributeMapper::instance()
550 {
551     if (!mSelf) {
552         (void)new DNAttributeMapper();
553     }
554     return mSelf;
555 }
556 
name2label(const QString & s) const557 QString Kleo::DNAttributeMapper::name2label(const QString &s) const
558 {
559     const std::map<const char *, const char *, ltstr>::const_iterator it
560         = d->map.find(s.trimmed().toUpper().toLatin1().constData());
561     if (it == d->map.end()) {
562         return QString();
563     }
564     return i18n(it->second);
565 }
566 
names() const567 QStringList Kleo::DNAttributeMapper::names() const
568 {
569     QStringList result;
570     for (std::map<const char *, const char *, ltstr>::const_iterator it = d->map.begin(); it != d->map.end(); ++it) {
571         result.push_back(QLatin1String(it->first));
572     }
573     return result;
574 }
575 
attributeOrder() const576 const QStringList &Kleo::DNAttributeMapper::attributeOrder() const
577 {
578     return d->attributeOrder;
579 }
580 
setAttributeOrder(const QStringList & order)581 void Kleo::DNAttributeMapper::setAttributeOrder(const QStringList &order)
582 {
583     d->attributeOrder = order.empty() ? defaultOrder : order;
584 
585     KConfigGroup config(KSharedConfig::openConfig(), "DN");
586     config.writeEntry("AttributeOrder", order);
587 }
588 
configWidget(QWidget * parent) const589 Kleo::DNAttributeOrderConfigWidget *Kleo::DNAttributeMapper::configWidget(QWidget *parent) const
590 {
591     return new DNAttributeOrderConfigWidget(mSelf, parent);
592 }
593