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