Coverage Report

Created: 2026-10-28 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensc/src/ui/notify.c
Line
Count
Source
1
/*
2
 * notify.c: Notification implementation
3
 *
4
 * Copyright (C) 2017 Frank Morgner <frankmorgner@gmail.com>
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
#ifdef HAVE_CONFIG_H
22
#include "config.h"
23
#endif
24
25
#include "notify.h"
26
27
#if defined(ENABLE_NOTIFY) && (defined(__APPLE__))
28
29
#include "libopensc/internal.h"
30
#include "libopensc/log.h"
31
#include <signal.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include <sys/wait.h>
35
#include <unistd.h>
36
37
static pid_t child = -1;
38
39
void sc_notify_init(void)
40
{
41
}
42
43
void sc_notify_close(void)
44
{
45
  if (child > 0) {
46
    int i, status;
47
    for (i = 0; child != waitpid(child, &status, WNOHANG); i++) {
48
      switch (i) {
49
        case 0:
50
          kill(child, SIGKILL);
51
          break;
52
        case 1:
53
          kill(child, SIGTERM);
54
          break;
55
        default:
56
          /* SIGTERM was our last resort */
57
          return;
58
      }
59
      usleep(100);
60
    }
61
    child = -1;
62
  }
63
}
64
65
#endif
66
67
#if defined(ENABLE_NOTIFY) && defined(_WIN32)
68
69
#include "common/compat_strlcpy.h"
70
#include "invisible_window.h"
71
#include <shellapi.h>
72
#include <shlwapi.h>
73
74
// {83C35893-99C6-4600-BFDB-45925C53BDD9}
75
static const GUID myGUID = { 0x83c35893, 0x99c6, 0x4600, { 0xbf, 0xdb, 0x45, 0x92, 0x5c, 0x53, 0xbd, 0xd9 } };
76
HINSTANCE sc_notify_instance = NULL;
77
HWND sc_notify_hwnd = NULL;
78
BOOL delete_icon = TRUE;
79
#define IDI_SMARTCARD       102
80
#define WMAPP_NOTIFYCALLBACK (WM_APP + 1)
81
static BOOL RestoreTooltip();
82
void ShowContextMenu(HWND hwnd, POINT pt);
83
84
// we need commctrl v6 for LoadIconMetric()
85
#include <commctrl.h>
86
87
static int GetMonitorScalingRatio(void)
88
{
89
  POINT pt = { 0, 0 };
90
  HMONITOR monitor;
91
  MONITORINFOEX info = { .cbSize = sizeof(MONITORINFOEX) };
92
  DEVMODE devmode = { .dmSize = sizeof(DEVMODE) };
93
  monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
94
  if (!monitor)
95
    goto err;
96
  if (!GetMonitorInfo(monitor, (LPMONITORINFO)&info))
97
    goto err;
98
  if (!EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &devmode))
99
    goto err;
100
  if (info.rcMonitor.left == info.rcMonitor.right)
101
    goto err;
102
  return 100*devmode.dmPelsWidth/(info.rcMonitor.right - info.rcMonitor.left);
103
err:
104
  return 100;
105
}
106
107
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
108
{
109
  switch (message) {
110
    case WM_COMMAND:
111
      {
112
        int const wmId = LOWORD(wParam);
113
        // Parse the menu selection:
114
        switch (wmId)
115
        {
116
          case WMAPP_EXIT:
117
            break;
118
119
          default:
120
            return DefWindowProc(hwnd, message, wParam, lParam);
121
        }
122
      }
123
      break;
124
125
    case WMAPP_NOTIFYCALLBACK:
126
      switch (LOWORD(lParam)) {
127
        case NIN_BALLOONTIMEOUT:
128
        case NIN_BALLOONUSERCLICK:
129
          RestoreTooltip();
130
          break;
131
132
        case WM_CONTEXTMENU:
133
          {
134
            int scaling = GetMonitorScalingRatio();
135
            POINT const pt = { 100*LOWORD(wParam)/scaling, 100*HIWORD(wParam)/scaling };
136
            ShowContextMenu(hwnd, pt);
137
          }
138
          break;
139
      }
140
      break;
141
142
    default:
143
      return DefWindowProc(hwnd, message, wParam, lParam);
144
  }
145
146
    return 0;
147
}
148
149
static const char* lpszClassName = "NOTIFY_CLASS";
150
151
static BOOL AddNotificationIcon(void)
152
{
153
  NOTIFYICONDATA nid = {0};
154
  TCHAR path[MAX_PATH]={0};
155
  BOOL r;
156
  HMODULE hComctl = NULL;
157
  HRESULT(WINAPI * pfnLoadIconMetric)(HINSTANCE, PCWSTR, int, HICON *) = NULL;
158
159
  sc_notify_hwnd = create_invisible_window(lpszClassName, WndProc,
160
      sc_notify_instance);
161
  if (!sc_notify_hwnd) {
162
    return FALSE;
163
  }
164
165
  if (NULL == (hComctl = LoadLibraryA("comctl32.dll")) || NULL == (pfnLoadIconMetric = (HRESULT(WINAPI *)(HINSTANCE, PCWSTR, int, HICON *))(void (*)(void))GetProcAddress(hComctl, "LoadIconMetric")) || S_OK != pfnLoadIconMetric(sc_notify_instance, MAKEINTRESOURCEW(IDI_SMARTCARD), LIM_SMALL, &nid.hIcon)) {
166
    nid.hIcon = (HICON)LoadImage(sc_notify_instance, MAKEINTRESOURCE(IDI_SMARTCARD), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
167
  }
168
  if (hComctl) {
169
    FreeLibrary(hComctl);
170
  }
171
172
  nid.cbSize = sizeof(NOTIFYICONDATA);
173
  nid.hWnd = sc_notify_hwnd;
174
  // add the icon, setting the icon, tooltip, and callback message.
175
  // the icon will be identified with the GUID
176
  nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID;
177
  nid.guidItem = myGUID;
178
  nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
179
  if (GetModuleFileName(NULL, path, ARRAYSIZE(path))) {
180
    const char *basename = strrchr(path, '\\');
181
    if (basename) {
182
      basename++;
183
      if (0 != strcmp(basename, "opensc-notify.exe")) {
184
        /* Allow creation of system tray icon only for
185
         * "opensc-notify.exe" to avoid creation of the same icon by
186
         * multiple processes. */
187
        delete_icon = FALSE;
188
        return FALSE;
189
      }
190
    }
191
    strlcpy(nid.szTip, path, ARRAYSIZE(nid.szTip));
192
  } else {
193
    strcpy(nid.szTip, PACKAGE_NAME);
194
  }
195
196
  r = Shell_NotifyIcon(NIM_ADD, &nid);
197
  if (TRUE == r) {
198
    nid.uVersion = NOTIFYICON_VERSION_4;
199
    r = Shell_NotifyIcon(NIM_SETVERSION, &nid);
200
  }
201
202
  return r;
203
}
204
205
static BOOL DeleteNotificationIcon(void)
206
{
207
  BOOL r;
208
  NOTIFYICONDATA nid = {0};
209
210
  if (!delete_icon) {
211
    return FALSE;
212
  }
213
214
  nid.cbSize = sizeof(NOTIFYICONDATA);
215
  nid.uFlags = NIF_GUID;
216
  nid.guidItem = myGUID; 
217
218
  r  = Shell_NotifyIcon(NIM_DELETE, &nid);
219
220
  r &= delete_invisible_window(sc_notify_hwnd, lpszClassName,
221
      sc_notify_instance);
222
  sc_notify_hwnd = NULL;
223
224
  return r;
225
}
226
227
static BOOL RestoreTooltip()
228
{
229
    // After the balloon is dismissed, restore the tooltip.
230
  NOTIFYICONDATA nid = {0};
231
232
  nid.cbSize = sizeof(NOTIFYICONDATA);
233
    nid.uFlags = NIF_SHOWTIP | NIF_GUID;
234
  nid.guidItem = myGUID; 
235
236
    return Shell_NotifyIcon(NIM_MODIFY, &nid);
237
}
238
239
void ShowContextMenu(HWND hwnd, POINT pt)
240
{
241
  HMENU hMenu;
242
  hMenu=CreatePopupMenu();
243
244
    if (hMenu) {
245
    AppendMenu(hMenu, MF_STRING, WMAPP_EXIT, ui_get_str(NULL, NULL, NULL, NOTIFY_EXIT));
246
247
    // our window must be foreground before calling TrackPopupMenu or the menu will not disappear when the user clicks away
248
    SetForegroundWindow(hwnd);
249
250
    // respect menu drop alignment
251
    UINT uFlags = TPM_RIGHTBUTTON;
252
    if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) {
253
      uFlags |= TPM_RIGHTALIGN;
254
    } else {
255
      uFlags |= TPM_LEFTALIGN;
256
    }
257
258
    TrackPopupMenuEx(hMenu, uFlags, pt.x, pt.y, hwnd, NULL);
259
  }
260
}
261
262
263
static void notify_shell(struct sc_context *ctx,
264
    const char *title, const char *text, LPCTSTR icon_path, int icon_index)
265
{
266
  NOTIFYICONDATA nid = {0};
267
  HICON icon = NULL;
268
269
  nid.cbSize = sizeof(NOTIFYICONDATA);
270
    nid.uFlags = NIF_GUID;
271
    nid.guidItem = myGUID;
272
273
  if (title) {
274
    strlcpy(nid.szInfoTitle, title, ARRAYSIZE(nid.szInfoTitle));
275
  }
276
  if (text) {
277
    nid.uFlags |= NIF_INFO;
278
    strlcpy(nid.szInfo, text, ARRAYSIZE(nid.szInfo));
279
  }
280
  if (icon_path) {
281
    ExtractIconEx(icon_path, icon_index, &icon, NULL, 1);
282
    if (icon) {
283
      nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
284
      nid.hBalloonIcon = icon;
285
    }
286
  }
287
288
    Shell_NotifyIcon(NIM_MODIFY, &nid);
289
290
  if (icon) {
291
    DestroyIcon(icon);
292
  }
293
}
294
295
void sc_notify_init(void)
296
{
297
  if (!sc_notify_instance) {
298
    /* returns the HINSTANCE of the exe. If the code executes in a DLL,
299
     * sc_notify_instance_notify should be pre-initialized */
300
    sc_notify_instance = GetModuleHandle(NULL);
301
  }
302
  AddNotificationIcon();
303
}
304
305
void sc_notify_close(void)
306
{
307
  DeleteNotificationIcon();
308
}
309
310
void sc_notify(const char *title, const char *text)
311
{
312
  notify_shell(NULL, title, text, NULL, 0);
313
}
314
315
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
316
    struct sc_pkcs15_card *p15card, enum ui_str id)
317
{
318
  const char *title, *text;
319
  LPCTSTR icon_path = NULL;
320
  int icon_index = 0;
321
  title = ui_get_str(ctx, atr, p15card, id);
322
  text = ui_get_str(ctx, atr, p15card, id+1);
323
324
  switch (id) {
325
    case NOTIFY_CARD_INSERTED:
326
      icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll");
327
      icon_index = 3;
328
      break;
329
    case NOTIFY_CARD_REMOVED:
330
      icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll");
331
      icon_index = 2;
332
      break;
333
    case NOTIFY_PIN_GOOD:
334
      icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll");
335
      icon_index = 16;
336
      break;
337
    case NOTIFY_PIN_BAD:
338
      icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll");
339
      icon_index = 11;
340
      break;
341
    default:
342
      break;
343
  }
344
345
  notify_shell(ctx, title, text, icon_path, icon_index);
346
}
347
348
#elif defined(ENABLE_NOTIFY) && defined(__APPLE__)
349
350
static void notify_proxy(struct sc_context *ctx,
351
    const char *title, const char* subtitle,
352
    const char *text, const char *icon, const char *sound,
353
    const char *group)
354
{
355
  /* terminal-notifier does not reliably activate keychain when clicked on
356
   * the notification
357
   * (https://github.com/julienXX/terminal-notifier/issues/196), that's why
358
   * we're including NotificationProxy which has similar features */
359
  const char notificationproxy[] = "/Library/OpenSC/Applications/NotificationProxy.app/Contents/MacOS/NotificationProxy";
360
361
  if (ctx && ctx->app_name
362
      && (0 == strcmp(ctx->app_name, "opensc-pkcs11")
363
        || 0 == strcmp(ctx->app_name, "onepin-opensc-pkcs11"))) {
364
    /* some programs don't like forking when loading our PKCS#11 module,
365
     * see https://github.com/OpenSC/OpenSC/issues/1174.
366
     * TODO implementing an XPC service which sends the notification should
367
     * work, though. See
368
     * https://github.com/OpenSC/OpenSC/issues/1304#issuecomment-376656003 */
369
    return;
370
  }
371
372
  if (child > 0) {
373
    int status;
374
    if (0 == waitpid(child, &status, WNOHANG)) {
375
      kill(child, SIGKILL);
376
      usleep(100);
377
      if (0 == waitpid(child, &status, WNOHANG)) {
378
        sc_log(ctx, "Can't kill %ld, skipping current notification", (long) child);
379
        return;
380
      }
381
    }
382
  }
383
384
  child = fork();
385
  switch (child) {
386
    case 0:
387
      /* child process */
388
389
      /* for some reason the user _tokend can call brew's installation of
390
       * terminal-notifier, but it cannot call `/usr/bin/open` with
391
       * NotificationProxy.app that we're shipping... However, while
392
       * `sudo -u _tokend /usr/local/bin/terminal-notifier -title test`
393
       * works in the terminal, it hangs when executed from the tokend
394
       * process.  For now, we try to deliver the notification anyway
395
       * making sure that we are waiting for only one forked process. */
396
      if (0 > execl(notificationproxy, notificationproxy,
397
            title ? title : "",
398
            subtitle ? subtitle : "",
399
            text ? text : "",
400
            icon ? icon : "",
401
            group ? group : "",
402
            sound ? sound : "",
403
            (char *) NULL)) {
404
        perror("exec failed");
405
        exit(0);
406
      }
407
      break;
408
    case -1:
409
      sc_log(ctx, "failed to fork for notification");
410
      break;
411
    default:
412
      if (ctx) {
413
        sc_log(ctx, "Created %ld for notification:", (long) child);
414
        sc_log(ctx, "%s %s %s %s %s %s %s", notificationproxy,
415
            title ? title : "",
416
            subtitle ? subtitle : "",
417
            text ? text : "",
418
            icon ? icon : "",
419
            group ? group : "",
420
            sound ? sound : "");
421
      }
422
      break;
423
  }
424
}
425
426
void sc_notify(const char *title, const char *text)
427
{
428
  notify_proxy(NULL, title, NULL, text, NULL, NULL, NULL);
429
}
430
431
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
432
    struct sc_pkcs15_card *p15card, enum ui_str id)
433
{
434
  const char *title, *text, *icon, *group;
435
  title = ui_get_str(ctx, atr, p15card, id);
436
  text = ui_get_str(ctx, atr, p15card, id+1);
437
438
  if (p15card && p15card->card && p15card->card->reader) {
439
    group = p15card->card->reader->name;
440
  } else {
441
    group = ctx ? ctx->app_name : NULL;
442
  }
443
444
  switch (id) {
445
    case NOTIFY_CARD_INSERTED:
446
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/VCard.icns";
447
      break;
448
    case NOTIFY_CARD_REMOVED:
449
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/EjectMediaIcon.icns";
450
      break;
451
    case NOTIFY_PIN_GOOD:
452
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/UnlockedIcon.icns";
453
      break;
454
    case NOTIFY_PIN_BAD:
455
      icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/LockedIcon.icns";
456
      break;
457
    default:
458
      icon = NULL;
459
      break;
460
  }
461
462
  notify_proxy(ctx, title, NULL, text, icon, NULL, group);
463
}
464
465
#elif defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2)
466
467
#include <gio/gio.h>
468
#include "libopensc/log.h"
469
470
static GApplication *application = NULL;
471
472
void sc_notify_init(void)
473
{
474
  if (!application) {
475
    application = g_application_new("org.opensc.notify", G_APPLICATION_NON_UNIQUE);
476
  }
477
  if (application && FALSE == g_application_get_is_registered(application)) {
478
    g_application_register(application, NULL, NULL);
479
  }
480
}
481
482
void sc_notify_close(void)
483
{
484
  if (application) {
485
    g_object_unref(application);
486
    application = NULL;
487
  }
488
}
489
490
static void notify_gio(struct sc_context *ctx,
491
    const char *title, const char *text, const char *icon,
492
    const char *group)
493
{
494
  if (application
495
      && g_application_get_is_registered(application)
496
      && g_application_get_dbus_connection(application)) {
497
    GIcon *gicon = NULL;
498
    GNotification *notification = g_notification_new(title);
499
    if (!notification) {
500
      return;
501
    }
502
503
    if (text) {
504
      g_notification_set_body(notification, text);
505
    }
506
    if (icon) {
507
      gicon = g_themed_icon_new(icon);
508
      if (gicon) {
509
        g_notification_set_icon(notification, gicon);
510
      }
511
    }
512
513
    if (ctx) {
514
      sc_log(ctx, "%s %s %s %s",
515
          title ? title : "",
516
          text ? text : "",
517
          icon ? icon : "",
518
          group ? group : "");
519
    }
520
521
    g_application_send_notification(application, group, notification);
522
523
    if (gicon) {
524
      g_object_unref(gicon);
525
    }
526
    g_object_unref(notification);
527
  }
528
}
529
530
#else
531
532
0
void sc_notify_init(void) {}
533
0
void sc_notify_close(void) {}
534
0
void sc_notify(const char *title, const char *text) {}
535
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
536
215
    struct sc_pkcs15_card *p15card, enum ui_str id) {}
537
538
#endif
539
540
#if defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2)
541
void sc_notify(const char *title, const char *text)
542
{
543
  notify_gio(NULL, title, text, NULL, NULL);
544
}
545
546
void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr,
547
    struct sc_pkcs15_card *p15card, enum ui_str id)
548
{
549
  const char *title, *text, *icon, *group;
550
  title = ui_get_str(ctx, atr, p15card, id);
551
  text = ui_get_str(ctx, atr, p15card, id+1);
552
553
  if (p15card && p15card->card && p15card->card->reader) {
554
    group = p15card->card->reader->name;
555
  } else {
556
    group = ctx ? ctx->app_name : NULL;
557
  }
558
559
  switch (id) {
560
    case NOTIFY_CARD_INSERTED:
561
      icon = "contact-new";
562
      break;
563
    case NOTIFY_CARD_REMOVED:
564
      icon = "media-eject";
565
      break;
566
    case NOTIFY_PIN_GOOD:
567
      icon = "changes-allow";
568
      break;
569
    case NOTIFY_PIN_BAD:
570
      icon = "changes-prevent";
571
      break;
572
    default:
573
      icon = NULL;
574
      break;
575
  }
576
577
  notify_gio(ctx, title, text, icon, group);
578
}
579
580
#endif