Archive | Weizen

09 März 2010 ~ 3 Comments | ÄHNLICHE ARTIKEL

Die VooDolls kommen nach Deutschland

Am Mittwoch wurde ich von der für Deutschland zuständigen Country-Managerin der Firma Skytale AB angeschrieben, die mich fragte, ob ich nicht mal ihr neues Produkt – die VooDolls – unter die Lupe nehmen möchte. Als Dankeschön könnte ich dafür die mir zugeschickten Exemplare behalten. :-)

Ich habe zugestimmt und aus dem ziemlich großen Sortiment ein paar Voodoo-Puppen ausgewählt: Die Figuren wurden am Donnerstag in den Versand gegeben und kamen am Montag bereits bei mir an. Abgesendet wurden sie in Schweden. ;-)


Meine Auswahl

Jede der Puppen hat angeblich magische Kräfte, die man durch das Aussprechen eines Zauberspruches und das Durchbohren der Puppe mit einer Nadel freisetzen kann. Ausprobiert habe ich es noch nicht – dafür sind mir die Dinger zu schade :D ! Jede Figur kommt mit einem kleinen Beutel, in den man sie stecken kann, und mit einer Karte, auf der die Fähigkeit der Puppe sowie der passende Zauberspruch steht.

Viel interessanter als die Voodoo-Magie finde ich allerdings den Designfaktor. Die VooDolls sind nämlich komplett aus einem Garn geschnürt (was man besonders gut am Kopf erkennen kann). Der Kopf ist zudem durch zusätzliche Schnüre mit dem restlichen Körper verbunden. Bei einem kurzen Belastungstest konnte ich keine Probleme mit der Befestigung feststellen.

Als echte Voodoo-Puppen werden die kleinen VooDolls warscheinlich hierzulande keine Anhänger finden – ich denke, das ist auch garnicht wirklich beabsichtigt. Ich könnte mir jedoch gut vorstellen, dass sie sehr schnell viele Freunde finden werden, die sie an ihren Taschen, Rucksäcken oder sogar am Handy mit sich herumtragen :-) . Designtechnisch sind die Puppen jedenfalls sehr liebevoll gestaltet und haben ihren ganz eigenen Charme. :D

Sehr cool ist übrigens auch das “Quiz“, bei dem man Vorschläge für neue, länderspezifische VooDolls machen kann. Wie wäre es z.B. mit einer “Angela Merkel”-VooDoll oder mit einem “Günther Jauch”? Ich bin gespannt, was da in der deutschen VooDolls-Landschaft demnächst für neue Charaktere und Fähigkeiten auftauchen werden.

Was haltet ihr von den VooDolls? Könntet ihr sie euch als Accessoire vorstellen? Hättet ihr auch das Problem, dass ihr euch nicht entscheiden könnt :) ? Würdet ihr eventuell sogar mal die VooDoo-Kräfte ausprobieren wollen :D ?

VooDoo-Grüße, Kenny




Natürlich wollen auch Schwangere im Winter warm verpackt und gegen Schnee und Kälte geschützt sein. Winterjacken, speziell hergestellt für Schwangere sind etwas ganz Besonderes und nicht leicht zu erwerben, da man auf die Frage, ob es Winterjacken für Schwangere gibt, häufig nur ein genervtes "Nein, haben wir nicht bekommt. Jetzt ist es wieder so weit Die neue Schwangerschaftsmode ist da! Wunderbar lang geschnitten, um den Bereich um Hüften und Po, sowie den Bauch vor Kälte zu schützen. Alle Modelle sind mit zahlreichen Taschen versehen, sodass Sie auch einige Accessoires für Sie und Ihr Baby einpacken können. Jedes Modell gibt es in mehreren Farben und auch in großen Größen.
Einige Winterjacken haben Kapuzen, die Sie vor jedem Wind schützen und durch ihren raffinierten Schnitt können einige Jacken sogar nach der Schwangerschaft getragen werden, ohne dass etwas auffällt. So kann die Jacke auch später noch verwendet werden und man muss für den nächsten Winter keine neue Jacke kaufen.



09 März 2010 ~ 0 Comments | ÄHNLICHE ARTIKEL

[Update] hMailServer-Konfiguration via CGI

Es ist endlich geschafft! Nachdem ich die letzten Tage überlegt habe, wie ich das ganze am unkompliziertesten löse, habe ich nun eine Möglichkeit gefunden: Die Rede ist von der Verwaltung eines hMailServer Mailaccounts.

In den letzten Tagen hatte ich euch VBScripte vorgestellt, mit denen ein Benutzer sein Passwort ändern oder eine Mailumleitung einrichten. Das Problem bestand bisher, diese Funktionen auch remote verfügbar zu machen – denn direkten Zugriff auf den Server wird keiner der Mailbenutzer kriegen. ;-)

Im Moment verwende ich immernoch einen ziemlich minimalistischen Webserver auf dem Rechner – trotzdem wollte ich die Konfiguration bereits lauffähig machen. Die einzige Möglichkeit hierfür war die Verwendung eines CGI-Scriptes.
Also habe ich mich auf die Suche gemacht, wie ich ein VBScript über CGI ansprechen und ausführen kein. Dummerweise funktionierte das Codebeispiel bei mir überhaupt nicht! Eine andere Lösung musste also her…

…und diese habe ich hier gefunden. Anstatt eines CGI-Scriptes habe ich einfach eine CGI-Anwendung in Delphi geschrieben :D ! Dadurch habe ich zum einen gelernt, wie man sowas macht und zum anderen konnte ich das ganze in einer robusten, mir bekannten Sprache erstellen.

Wo ihr ein bisschen aufpassen müsst, ist bei dem Herleiten der Dateipfade: Irgendwie habe ich da das Gefühl, dass der Server die Werte PATH_INFO, PATH_TRANSLATED und SCRIPT_NAME nicht korrekt setzt. Aber das müsste man nochmal durch das Testen mit anderen Webservern überprüfen.

Hier jedenfalls erstmal der Quelltext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
unit MainForm;

interface

uses
  Windows,
  SysUtils,
  Messages,
  HTTPApp,
  Classes;


type
  TValueAction = (vaUnknown, vaForward, vaPassword);

  TValueRecord = record
    Action         : TValueAction;
    Domain         : String;
    Username       : String;
    Password       : String;
    ForwardAddress : String;
    NewPassword    : String;
  end;

  TMainWebModule = class(TWebModule)
    procedure MainWebModuledefaultAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  private
    { Private-Deklarationen }
    function GetApplicationOutput(AApplication : String; AParameters : TStringList) : String;
    function GetCGIPath(APathTranslated : String; APathInfo : String; AScriptName : String) : String;
    function GetResultString(AAction : TValueAction; AResult : String) : String;
    function GetWindowsPath : String;
    function ParseRequest(ARequest : TWebRequest) : TValueRecord;
  public
    { Public-Deklarationen }
  end;

var
  MainWebModule : TMainWebModule;

implementation

{$R *.xfm}

const
  CCGIFolder       = '/cgi-bin';
  CCscriptExe      = 'system32\cscript.exe';
  CForwardAction   = 'forward';
  CForwardParam    = 'forward';
  CForwardVBS      = 'changeForward.vbs';
  CNewParam        = 'new';
  CNoLogoParam     = '/nologo';
  CPasswordAction  = 'password';
  CPasswordParam   = 'password';
  CPasswordVBS     = 'changePassword.vbs';
  CPathDivider     = '\';
  CQuote           = '"';
  CQuoteEscaped    = '\"';
  CURLDivider      = '/';

// source: http://delphi.about.com/cs/adptips2001/a/bltip0201_2.htm
function TMainWebModule.GetApplicationOutput(AApplication: String; AParameters: TStringList): String;
  function GetParameterLine(AApplication : String; AParameters : TStringList) : String;
  var
    LIndex : Integer;
  begin
    Result := CQuote + StringReplace(AApplication, CQuote, CQuoteEscaped, [rfReplaceAll, rfIgnoreCase]) + CQuote;

    if (AParameters <> nil) then
    begin
      for LIndex := 0 to Pred(AParameters.Count) do
        Result := Result + #32 + CQuote + StringReplace(AParameters[LIndex], CQuote, CQuoteEscaped, [rfReplaceAll, rfIgnoreCase]) + CQuote;
    end;
  end;

  procedure ProcessMessages;
  var
    LMessage : TMsg;
  begin
    while PeekMessage(LMessage, 0, 0, 0, PM_REMOVE) do
    begin
      if (LMessage.Message <> WM_QUIT) then
      begin
        TranslateMessage(LMessage);
        DispatchMessage(LMessage);
      end
      else
        Break;
    end;
  end;
const
  CReadBuffer = 1024;
var
  LAppRunning  : DWord;
  LBuffer      : PChar;
  LBytesRead   : DWord;
  LProcessInfo : TProcessInformation;
  LReadPipe    : THandle;
  LSecurity    : TSecurityAttributes;
  LStart       : TStartUpInfo;
  LWritePipe   : THandle;
begin
  Result := '';

  LSecurity.nLength             := SizeOf(TSecurityAttributes);
  LSecurity.bInheritHandle      := true;
  LSecurity.lpSecurityDescriptor := nil;

  if CreatePipe(LReadPipe, LWritePipe, @LSecurity, 0) then
  begin
    try
      LBuffer := AllocMem(Succ(CReadBuffer));
      try
        FillChar(LStart, SizeOf(LStart), #0) ;
        LStart.cb          := SizeOf(LStart) ;
        LStart.dwFlags     := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
        LStart.hStdInput   := LReadPipe;
        LStart.hStdOutput  := LWritePipe;
        LStart.wShowWindow := SW_HIDE;

        if CreateProcess(nil, PChar(GetParameterLine(AApplication, AParameters)),
                         @LSecurity, @LSecurity, true, NORMAL_PRIORITY_CLASS, nil,
                         nil, LStart, LProcessInfo) then
        begin
          try
            repeat
              LAppRunning := WaitForSingleObject(LProcessInfo.hProcess, 100);
             
              ProcessMessages;
            until (LAppRunning <> WAIT_TIMEOUT);

            repeat
              LBytesRead := 0;
              ReadFile(LReadPipe, LBuffer[0], CReadBuffer, LBytesRead, nil) ;
              LBuffer[LBytesRead] := #0;
              OemToChar(LBuffer, LBuffer);

              Result := Result + String(LBuffer);
            until (LBytesRead < CReadBuffer);
          finally
            CloseHandle(LProcessInfo.hProcess);
            CloseHandle(LProcessInfo.hThread);
          end;
        end;
      finally
        FreeMem(LBuffer);
      end;
    finally
      CloseHandle(LReadPipe);
      CloseHandle(LWritePipe);
    end;
  end;
end;

function TMainWebModule.GetCGIPath(APathTranslated, APathInfo, AScriptName: String): String;
var
  LPosition : Integer;
begin
  Result := '';

  repeat
    LPosition := Pos(CURLDivider, APathInfo);
    if (LPosition > 0) then
      APathInfo[LPosition] := CPathDivider;
  until (LPosition <= 0);

  repeat
    LPosition := Pos(CURLDivider, AScriptName);
    if (LPosition > 0) then
      AScriptName[LPosition] := CPathDivider;
  until (LPosition <= 0);

  if (Pred(Pos(APathInfo, AScriptName) + Length(APathInfo)) = Length(AScriptName)) then
  begin
    Delete(AScriptName, Succ(Length(AScriptName) - Length(APathInfo)), Length(APathInfo));

    if (Pred(Pos(APathInfo, APathTranslated) + Length(APathInfo)) = Length(APathTranslated)) then
    begin
      Delete(APathTranslated, Succ(Length(APathTranslated) - Length(APathInfo)), Length(APathInfo));

      Result := APathTranslated + AScriptName + CPathDivider;
    end;
  end;
end;

function TMainWebModule.GetResultString(AAction: TValueAction; AResult: String) : String;
const
  CEverythingFine   = 'everything went fine';
  CForward          = '(forward)';
  CInternalError    = 'an internal error occured';
  CMissingArguments = 'missing arguments';
  CNoSuchDomain     = 'there is no such domain';
  CPassword         = '(password)';
  CUnknownError     = 'an unknown error occured';
  CWrongCredentials = 'wrong credentials have been provided';
var
  LError  : LongInt;
  LResult : Byte;
begin
  Result := '';

  AResult := Trim(AResult);
  Val(AResult, LResult, LError);
  if (LError = 0) then
  begin
    case AAction of
      vaForward :
      begin
        case LResult of
          0 : Result := AResult + #32 + CEverythingFine + #32 + CForward;
          1 : Result := AResult + #32 + CWrongCredentials + #32 + CForward;
          2 : Result := AResult + #32 + CWrongCredentials + #32 + CForward;
          3 : Result := AResult + #32 + CNoSuchDomain + #32 + CForward;
          4 : Result := AResult + #32 + CWrongCredentials + #32 + CForward;
          5 : Result := AResult + #32 + CInternalError + #32 + CForward;
          6 : Result := AResult + #32 + CMissingArguments + #32 + CForward;
        else
          Result := AResult + #32 + CUnknownError + #32 + CForward;
        end;
      end;

      vaPassword :
      begin
        case LResult of
          0 : Result := AResult + #32 + CEverythingFine + #32 + CPassword;
          1 : Result := AResult + #32 + CWrongCredentials + #32 + CPassword;
          2 : Result := AResult + #32 + CWrongCredentials + #32 + CPassword;
          3 : Result := AResult + #32 + CNoSuchDomain + #32 + CPassword;
          4 : Result := AResult + #32 + CWrongCredentials + #32 + CPassword;
          5 : Result := AResult + #32 + CInternalError + #32 + CPassword;
          6 : Result := AResult + #32 + CMissingArguments + #32 + CPassword;
        else
          Result := AResult + #32 + CUnknownError + #32 + CPassword;
        end;
      end;
    else
      Result := AResult + #32 + CUnknownError;
    end;
  end
  else
    Result := AResult + #32 + CUnknownError;
end;

function TMainWebModule.GetWindowsPath: String;
var
  LSize : Integer;
begin
  Result := '';

  LSize := GetWindowsDirectory(nil, 0);
  if (LSize > 0) then
  begin
    SetLength(Result, Succ(LSize));
    GetWindowsDirectory(@Result[1], Length(Result));

    Result := Trim(Result);
    if (Length(Result) > 0) then
    begin
      if (Result[Length(Result)] <> CPathDivider) then
        Result := Result + CPathDivider;
    end;
  end;
end;

procedure TMainWebModule.MainWebModuledefaultAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
  LCGIDirectory : String;
  LParameters   : TStringList;
  LTextResult   : String;
  LValueRecord  : TValueRecord;
begin
  LCGIDirectory := GetCGIPath(Request.PathTranslated, Request.PathInfo, Request.ScriptName);
  if DirectoryExists(LCGIDirectory) then
  begin
    LValueRecord := ParseRequest(Request);

    case LValueRecord.Action of
      vaForward :
      begin
        if FileExists(LCGIDirectory + CForwardVBS) then
        begin
          LParameters := TStringList.Create;
          try
            LParameters.Add(CNoLogoParam);
            LParameters.Add(LCGIDirectory + CForwardVBS);
            if (Length(Trim(LValueRecord.Domain)) > 0) then
              LParameters.Add(LValueRecord.Domain);
            if (Length(Trim(LValueRecord.Username)) > 0) then
              LParameters.Add(LValueRecord.Username);
            if (Length(Trim(LValueRecord.Password)) > 0) then
              LParameters.Add(LValueRecord.Password);
            if (Length(Trim(LValueRecord.ForwardAddress)) > 0) then
              LParameters.Add(LValueRecord.ForwardAddress);

            LTextResult := Trim(GetApplicationOutput(GetWindowsPath + CCscriptExe, LParameters));
            Response.Content := GetResultString(LValueRecord.Action, LTextResult);
          finally
            LParameters.Free;
          end;
        end;
      end;

      vaPassword :
      begin
        if FileExists(LCGIDirectory + CPasswordVBS) then
        begin
          LParameters := TStringList.Create;
          try
            LParameters.Add(CNoLogoParam);
            LParameters.Add(LCGIDirectory + CPasswordVBS);
            if (Length(Trim(LValueRecord.Domain)) > 0) then
              LParameters.Add(LValueRecord.Domain);
            if (Length(Trim(LValueRecord.Username)) > 0) then
              LParameters.Add(LValueRecord.Username);
            if (Length(Trim(LValueRecord.Password)) > 0) then
              LParameters.Add(LValueRecord.Password);
            if (Length(Trim(LValueRecord.NewPassword)) > 0) then
              LParameters.Add(LValueRecord.NewPassword);

            LTextResult := Trim(GetApplicationOutput(GetWindowsPath + CCscriptExe, LParameters));
            Response.Content := GetResultString(LValueRecord.Action, LTextResult);
          finally
            LParameters.Free;
          end;
        end;
      end;
    else
      Response.Content := '';
    end;
  end;

  Handled := true;
end;

function TMainWebModule.ParseRequest(ARequest: TWebRequest) : TValueRecord;
var
  LAction     : String;
  LIndex      : Integer;
  LScriptName : String;
  LUsername   : String;
begin
  Result.Action         := vaUnknown;
  Result.Domain         := '';
  Result.Username       := '';
  Result.Password       := '';
  Result.ForwardAddress := '';
  Result.NewPassword    := '';

  if (ARequest <> nil) then
  begin
    LScriptName := AnsiLowerCase(Trim(ARequest.ScriptName));
    if (Length(LScriptName) > 0) then
    begin
      // kill trailing slash
      if (LScriptName[Length(LScriptName)] = CURLDivider) then
        Delete(LScriptName, Length(LScriptName), 1);

      // read trailing value - is username
      // kill trailing value
      for LIndex := Length(LScriptName) downto 1 do
      begin
        if (LScriptName[LIndex] = CURLDivider) then
        begin
          LUserName := Copy(LScriptName, Succ(LIndex), Length(LScriptName) - LIndex);
          Delete(LScriptName, LIndex, Length(LScriptName) - Pred(LIndex));

          Break;
        end;
      end;

      // read trailing value - is action
      // kill trailing value
      for LIndex := Length(LScriptName) downto 1 do
      begin
        if (LScriptName[LIndex] = CURLDivider) then
        begin
          LAction := Copy(LScriptName, Succ(LIndex), Length(LScriptName) - LIndex);
          Delete(LScriptName, LIndex, Length(LScriptName) - Pred(LIndex));

          Break;
        end;
      end;

      // check action string
      if AnsiSameText(LAction, CForwardAction) then
        Result.Action := vaForward;
      if AnsiSameText(LAction, CPasswordAction) then
        Result.Action := vaPassword;

      case Result.Action of
        vaForward :
        begin
          // read "forward" parameter
          LIndex := Request.QueryFields.IndexOfName(CForwardParam);
          if (LIndex >= 0) then
            Result.ForwardAddress := Request.QueryFields.ValueFromIndex[LIndex];

          // read "password" parameter
          LIndex := Request.QueryFields.IndexOfName(CPasswordParam);
          if (LIndex >= 0) then
            Result.Password := Request.QueryFields.ValueFromIndex[LIndex];

          Result.Domain   := Request.Host;
          Result.Username := LUserName;
        end;

        vaPassword :
        begin
          // read "new" parameter
          LIndex := Request.QueryFields.IndexOfName(CNewParam);
          if (LIndex >= 0) then
            Result.NewPassword := Request.QueryFields.ValueFromIndex[LIndex];

          // read "password" parameter
          LIndex := Request.QueryFields.IndexOfName(CPasswordParam);
          if (LIndex >= 0) then
            Result.Password := Request.QueryFields.ValueFromIndex[LIndex];

          Result.Domain   := Request.Host;
          Result.Username := LUserName;
        end;
      end;
    end;
  end;
end;

end.

In dem Programm selber gehen wir folgendermaßen vor:

  1. Wir parsen den URL-String.
  2. Wir führen das entsprechende VBScript aus.
  3. Wir lesen den Antwortwert aus stdout.
  4. Wir geben den erhaltenen Ergebniscode zurück.

Für den Zugriff habe ich mir ein tolles URL-Schema überlegt, das eingehalten werden muss, damit das Programm die richtigen Daten auslesen kann. Folgende Befehle gelten für das Setzen einer Mailumleitung:

1
2
https://[domain]/cgi-bin/[exedatei]/forward/[username]?password=[password]
https://[domain]/cgi-bin/[exedatei]/forward/[username]?password=[password]&forward=[email]

Durch das Setzen des forward-Parameters wird die Umleitung aktiviert. Durch Weglassen des Parameters wird die Umleitung deaktiviert. :-)

Für das Ändern des Passworts wird folgendes URL-Schema benutzt. Wenn man ein Mail-Forwarding einrichten kann, dann kann man auch sein Passwort ändern – die Befehle unterscheiden sich nur geringfügig:

1
https://[domain]/cgi-bin/[exedatei]/password]/[username]?password=[password]&new=[newpassword]

Was haltet ihr von dieser Form der Implementierung? Denkt ihr, ein Benutzer kann sich zwei URLs merke? Wie findet ihr den Lösungsansatz generell?

Update:
Der vorhandene Exploit wurde nun behoben. Das Problem war, dass die übergebenen Parameter nicht überprüft wurden, bevor sie in den auszuführenden Befehl eingebaut wurden. Nun werden Anführungszeichen escaped, um das Hinzufügen von zusätzlichen Parametern und andere Modifikationen des Befehls zu unterbinden (Stichwort Pipes).

Konfigurierende Grüße, Kenny

03 März 2010 ~ 4 Comments | ÄHNLICHE ARTIKEL

Mail-Forwarding des hMailServer ändern…

Und noch immer sitze ich am Webserver. Nachdem wir gestern entschieden haben, dass ich die Domains des Bezirks hosten werde, muss ich natürlich dafür sorgen, dass die Leute später nicht nur ihr Passwort ändern können, sondern, dass sie zumindest auch eine Mail-Umleitung einrichten können, falls sie nicht extra ein zusätzliches Postfach abrufen möchten.

Dabei ist wieder ein VBScript herausgekommen, das per cscript.exe aufgerufen werden muss. Es basiert großteilig auf dem Script, mit dem der Benutzer sein Passwort ändern kann.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  ' Wscript.Arguments(0) => domain name
 ' Wscript.Arguments(1) => username
 ' Wscript.Arguments(2) => password
 ' Wscript.Arguments(3) => forwarding mail address

  On Error Resume Next
  Err.Clear
 
  If Wscript.Arguments.Count = 3 Or Wscript.Arguments.Count = 4 Then
    Dim obApp
    Set obApp = CreateObject("hMailServer.Application")

    If Err.Number = 0 Then
      Call obApp.Authenticate(Wscript.Arguments(1) & "@" & Wscript.Arguments(0), Wscript.Arguments(2))
   
      If Err.Number = 0 Then
        Dim obDomain
        Set obDomain = obApp.Domains.ItemByName(Wscript.Arguments(0))
   
        If Err.Number = 0 Then
          Dim obAccount
          Set obAccount = obDomain.Accounts.ItemByAddress(Wscript.Arguments(1) & "@" & Wscript.Arguments(0))
   
          If Err.Number = 0 Then
            If obAccount.ValidatePassword(Wscript.Arguments(2)) Then
              If Wscript.Arguments.Count = 4 Then ' activate forwarding
               obAccount.ForwardAddress      = Wscript.Arguments(3)
                obAccount.ForwardEnabled      = true
                obAccount.ForwardKeepOriginal = false
              Else ' deactivate forwarding
               obAccount.ForwardAddress      = ""
                obAccount.ForwardEnabled      = false
                obAccount.ForwardKeepOriginal = true
              End If
              obAccount.Save

              WScript.StdOut.WriteLine "0" ' forwarding set
           Else
              WScript.StdOut.WriteLine "1" ' password is not correct
           End If
          Else
            WScript.StdOut.WriteLine "2" ' account does not exist
         End If
        Else
          WScript.StdOut.WriteLine "3" ' domain does not exist
       End If
      Else
        WScript.StdOut.WriteLine "4" ' password is not correct
      End If
    Else
      WScript.StdOut.WriteLine "5" ' hMailServer.Application not available
   End If
  Else
    WScript.StdOut.WriteLine "6" ' wrong number of arguments
 End If

Ich denke mal, dass damit die beiden wichtigsten Konfigurationsmöglichkeiten für eine E-Mail-Adresse abgedeckt werden sollten. :-)

Habt ihr eine Idee, was man als Endanwender sonst noch konfigurieren können sollte? Wie würdet ihr solch eine Konfiguration am liebsten vornehmen? Würde euch Telnet ausreichen oder würdet ihr eine Webseite bevorzugen?

Umleitende Grüße, Kenny

02 März 2010 ~ 3 Comments | ÄHNLICHE ARTIKEL

Ist der Winter endlich vorbei?

In letzter Zeit ist es ziemlich stürmisch in den deutschen Landen. Und erst vorhin bin ich in einen heftigen Schneeschauer geraten. Trotzdem schmilzt überall das Monate alte Eis und grüne Wiesen, Silvestermüll, Hundekot und erfrorene Tiere kommen ans Tageslicht.

Damit ihr jedoch nicht vergesst, wie schön sauber und weiß die Welt noch vor ein paar Tagen gewesen ist, wollte ich euch nochmal zwei schöne Bilder von Eiszapfen präsentieren! :D

Eiszapfen


Bei den Bildern wird es doch direkt wieder kühl im Zimmer. ;-)

Frühlingshafte Grüße, Kenny

21 Februar 2010 ~ 3 Comments | ÄHNLICHE ARTIKEL

SSH + VNC = sichere + Fernwartung

Wie man merkt, steht dieses Wochenende ganz im Zeichen der Informatik-Blogartikel :D . Keine Sorge, es wird auch wieder andere Zeiten geben, aber zur Zeit steht der Homeserver halt hoch im Kurs ;-) . Auch wenn jetzt alle Linux-Fanboys wieder anfangen zu meckern: Der Server läuft unter Windows (XP Pro SP3).

Linuxer werden damit wahrscheinlich kein Problem haben, aber unter Windows ist die Wartung eines Servers ohne grafische Oberfläche ziemlich schwierig, da sich einige Serveranwendungen garnicht über die Kommandozeile administrieren lassen. Eine Lösung musste her!

Mein erster Gedanke war, von außen über VPN einen Tunnels lokale Netzwerk herzustellen und dann im Netzwerk über die Windows-eigene Remotedesktopverbindung auf die grafische Oberfläche zuzugreifen. Das Einrichten von Windows als VPN-Server war überhaupt kein Problem – sowohl die Serverfunktion, als auch die Clientfunktion sind direkt im Betriebssystem enthalten. Allerdings… über den Router wollte das ganze dann nicht mehr funktionieren… trotz etlicher freigegebener Ports :-( .

Zum Glück eilte mir mein Freund @Baschdii (aka. @SBejga) vom rosa Riesen zur Hilfe und erklärte mir, dass man auch über SSH-Verbindung einen Tunnel herstellen kann. Das ist ganz praktisch, denn SSH lief bei mir ja bereits – ich musste in der Konfiguration des Servers (freeSSHd) nur noch die richtige Einstellung finden:

freeSSHd Tunneling

Danach habe ich überlegt, welches Remote-Desktop-Programm ich am besten verwende. Als Protokoll stand VNC relativ schnell fest – doch welche Implementation davon? Es gibt so viele… Nach einigen herben Enttäuschungen (ultraVNC stach dabei als besonders schreckliches Beispiel heraus) habe ich mich dann für realVNC entschieden. Selbst die äußerst eingeschränkte kostenlose Version reicht für meine Zwecke vollkommen aus :D ! Im Vergleich zu einigen Kontrahenten sind sowohl der Server, als auch der Client, ziemlich einfach zu konfigurieren. Um den Server nach außen hin abzusichern, bedarf es einer besonderen Einstellung:

Only accept connections from the local machine


Mit dieser Installation sind die Änderungen am Server bereits abgeschlossen. Nun ist noch der Client an der Reihe. Dieser muss mit 2 Programmen ausgerüstet werden, die man dank der Größe (ca. 6MB) eigentlich immer bei sich haben kann. :-)

Zuerst einmal benötigen wir ein Programm, mit dem wir den SSH-Tunnel zum Server aufbauen können. Für viele dürfte Putty als SSH-Client ein Begriff sein. Baschdii hat mich dagegen auf das Programm Tunnelier aufmerksam gemacht: Und in der Tat! Das Einrichten des Tunnels war ein Klacks :D ! Zuerst einmal trägt man im Login-Tab die SSH-Daten (Host, Port, Benutzername) ein:

Tunnelier: Login-Tab


Danach geht man auf den C2S-Tab und stellt dort den eigentlichen Tunnel ein. Die Konfiguration könnt ihr dem Screenshot entnehmen (Listen Interface = 127.0.0.1, List. Port = 5900, Destination Host = 127.0.0.1, Dest Port = 5900):

Tunnelier: C2S-Tab


Jetzt muss man über Tunnelier nur noch die SSH-Verbindung zum Server herstellen lassen und den realVNC-Client starten. Diesen kann man bereits normal benutzen – mit einer Ausnahme: Als Host muss die Adresse 127.0.0.1 angegeben werden!

realVNC-Client


Nach dem Klick auf “OK” passiert nun folgendes: Der “Viewer” verbindet sich mit dem lokalen Rechner und der Portnummer 5900. Dieser Port wurde von Tunnelier geöffnet und alle Daten, die der Viewer schickt, landen beim SSH-Client. Dieser schickt sie weiter an den SSH-Server, der wiederum bei sich lokal eine Verbindung zum Port 5900 aufgebaut hat – der Port, auf dem der echte realVNC-Server lauscht.
Auf diese Weise werden alle Daten, die die beiden VNC-Anwendungen austauschen über die SSH-Verbindung geleitet. Damit sollte ein Mitlesen oder gar Manipulieren der Daten durch Dritte effektiv verhindert werden :D !

Fernwartende Grüße, Kenny

21 Februar 2010 ~ 0 Comments | ÄHNLICHE ARTIKEL

[Update] Benutzerpasswort des hMailServer ändern…

Eigentlich wollte ich ja Mercury/32 auf meinem Heimserver einsetzen, jedoch hat das Programm mich maßlos enttäuscht. Als Ersatz habe ich für den hMailServer entschieden, der stabil läuft und obendrein auch noch wesentlich einfacher zu konfigurieren ist.

Die Sache funktioniert sogar so gut, dass ich überlege, ob ich nicht weitere Domains auf meinen Heimserver umziehen soll. Bei diesen anderen Domains würden allerdings auch externe Benutzer mit dabei sein: Und genau die würden Probleme machen! :-(

Nein, nicht was ihr denkt :D ! Wer sich die E-Mail-Protokolle (IMAP, POP3, SMTP) schonmal angegeguckt hat, der wird eins feststellen können: Ein Benutzer kann nicht einfach sein Passwort ändern!
Bei den großen Webmail-Anbietern kann man das über deren Webseite machen – das ist ein Zusatzfeature und von Betreiber zu Betreiber unterschiedlich. Doch wie wird das bei mir möglich sein? Eine Lösung musste her! Und die sieht derzeit so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  ' Wscript.Arguments(0) => domain name
 ' Wscript.Arguments(1) => username
 ' Wscript.Arguments(2) => old password
 ' Wscript.Arguments(3) => new password

  On Error Resume Next
  Err.Clear
 
  If Wscript.Arguments.Count = 4 Then
    Dim obApp
    Set obApp = CreateObject("hMailServer.Application")

    If Err.Number = 0 Then
      Call obApp.Authenticate(Wscript.Arguments(1) & "@" & Wscript.Arguments(0), Wscript.Arguments(2))
   
      If Err.Number = 0 Then
        Dim obDomain
        Set obDomain = obApp.Domains.ItemByName(Wscript.Arguments(0))
   
        If Err.Number = 0 Then
          Dim obAccount
          Set obAccount = obDomain.Accounts.ItemByAddress(Wscript.Arguments(1) & "@" & Wscript.Arguments(0))
   
          If Err.Number = 0 Then
            If obAccount.ValidatePassword(Wscript.Arguments(2)) Then
              obAccount.Password = Wscript.Arguments(3)  
              obAccount.Save

              WScript.StdOut.WriteLine "0" ' password set
           Else
              WScript.StdOut.WriteLine "1" ' old password is not correct
           End If
          Else
            WScript.StdOut.WriteLine "2" ' account does not exist
         End If
        Else
          WScript.StdOut.WriteLine "3" ' domain does not exist
       End If
      Else
        WScript.StdOut.WriteLine "4" ' old password is not correct
      End If
    Else
      WScript.StdOut.WriteLine "5" ' hMailServer.Application not available
   End If
  Else
    WScript.StdOut.WriteLine "6" ' wrong number of arguments
 End If

Hierbei handelt es sich um ein VBScript, das die COM-API benutzt, die der hMailServer bereitstellt. Dieses Script wird die Basis eines kleinen Servers werden, an dem man sich mit seiner E-Mail-Adresse und seinem Passwort anmelden und sein Passwort ändern können wird. Wie man das ganze mit SSL absichern kann, habe ich euch ja schon gezeigt. ;-)

Ja, für unerfahrene Benutzer könnte das Passwort ändern wirklich eine kleine Herausforderung werden – die geplante grafische Clientanwendung sollte die Hemmschwelle allerdings auf lange Sicht gesehen senken können. :-)

Update:
Mir ist gerade aufgefallen, dass ich eine wichtige Information vergessen habe! Und zwar muss das Script mit dem Windows Script Host cscript.exe aufgerufen werden. Ansonsten funktioniert die Ausgabe des Ergebniscodes nicht korrekt!

E-Mail-Grüße, Kenny

20 Februar 2010 ~ 0 Comments | ÄHNLICHE ARTIKEL

Stunnel: SSL für alles und jeden

Ich setze schon seit ein paar Tagen meinen Server komplett neu auf. Da der Server dieses Mal länger im Einsatz sein wird, habe ich mir natürlich auch überlegt, wie ich die Kiste ordentlich absichern kann. So biete ich zu allen Protokollen (z.B. IMAP, POP3, SMTP) auch die Varianten mit vorgeschaltetem SSL an (z.B. IMAPS, POP3S, SMTPS).

Wenn der Server direkt SSL unterstützt, ist das ganze garkein Problem. Dummerweise gibt es auch welche, bei denen das nicht der Fall ist. Das Projekt Stunnel kann dabei Abhilfe schaffen :D ! Das Programm fungiert als Reverse-Proxy, nimmt SSL-Verbindungen entgegen und leitet die Daten unverschlüsselt an den nichts ahnenden, lokalen Server weiter. :-)

So sehen bei mir z.B. die Definitionen für HTTPS, POP3S, IMAPS und SMTPS aus – alles ganz einfach. Beachtet werden muss nur, dass sich hinter der Portnummer, die unter “connect” angegeben ist, auch wirklich ein Server verbirgt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[https]
accept  = 443
connect = 80

[pop3s]
accept  = 995
connect = 110

[imaps]
accept  = 993
connect = 143

[smtps]
accept  = 465
connect = 25

Bisher konnte ich noch keine größeren Problem festsstellen. Das Programm scheint auch nicht ganz unbekannt zu sein. ;-)

Verschlüsselte Grüße, Kenny

20 Februar 2010 ~ 0 Comments | ÄHNLICHE ARTIKEL

Hooking mit uallCollection

Ich habe die letzten Tage an einem Projekt gearbeitet, für das ich systemweit API-Aufrufe hooken können musste. Leider ist dieses Unterfangen nicht so einfach, wie das, was ich euch letztes Mal präsentiert habe.

Im Internet finden sich einige Lösungen für dieses Problem: Angefangen beim Schreiben von Systemtreibern, bis hin zum Injizieren von Threads in bereits laufende Prozesse. Besonders gut scheint das Paket madHookCode zu sein, das leider weder günstig noch einfach zu haben ist.
Glücklicherweise gibt es die uallCollection, die die gleiche Methode wie das Paket madHookCode zu benutzen scheint. Auch sonst scheinen die beiden Projekte eng miteinander verknüpft zu sein – für den Programmierer, der sich zwischen den beiden Paketen entscheiden muss, ist das jedenfalls ein Vorteil, da die uallCollection komplett kostenlos ist. ;-)

Lediglich das Injizieren neu gestarteter Prozesse beherrscht die uallCollection nicht. Aber wozu hat man 10 gesunde Finger? Man kan sich relativ leicht eine Möglichkeit schaffen, um selber auf neue Prozesse zu reagieren:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
unit ProcessU;

interface

procedure HookProcess;
procedure UnhookProcess;

implementation

uses
  Windows,
  uallHook,
  MainU;

var
  VProcessLib : HMODULE = 0;

  VNewCreateProcessA : function (lpApplicationName: PChar; lpCommandLine: PChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VOldCreateProcessA : function (lpApplicationName: PChar; lpCommandLine: PChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VNewCreateProcessW : function (lpApplicationName: PWideChar; lpCommandLine: PWideChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VOldCreateProcessW : function (lpApplicationName: PWideChar; lpCommandLine: PWideChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;

function CatchCreateProcessA(lpApplicationName: PChar; lpCommandLine: PChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall; forward;
function CatchCreateProcessW(lpApplicationName: PWideChar; lpCommandLine: PWideChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall; forward;

function CatchCreateProcessA(lpApplicationName: PChar; lpCommandLine: PChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;
begin
  Result := VNewCreateProcessA(lpApplicationName, lpCommandLine, lpProcessAttributes,
                               lpThreadAttributes, bInheritHandles, dwCreationFlags,
                               lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                               lpProcessInformation);

  if Result then
    InjectLibrary(lpProcessInformation.dwProcessId, PAnsiChar(GetDLLFileName));
end;
function CatchCreateProcessW(lpApplicationName: PWideChar; lpCommandLine: PWideChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;
begin
  Result := VNewCreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes,
                               lpThreadAttributes, bInheritHandles, dwCreationFlags,
                               lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                               lpProcessInformation);

  if Result then
    InjectLibrary(lpProcessInformation.dwProcessId, PAnsiChar(GetDLLFileName));
end;

procedure HookProcess;
begin
  VProcessLib := GetModuleHandle(KERNEL32);
  if (VProcessLib <> 0) then
  begin
    @VOldCreateProcessA := GetProcAddress(VProcessLib, 'CreateProcessA');
    @VOldCreateProcessW := GetProcAddress(VProcessLib, 'CreateProcessW');

    HookCode(@VOldCreateProcessA, @CatchCreateProcessA, @VNewCreateProcessA);
    HookCode(@VOldCreateProcessW, @CatchCreateProcessW, @VNewCreateProcessW);
  end;
end;

procedure UnhookProcess;
begin
  if (VProcessLib <> 0) then
  begin
    VProcessLib := 0;

    UnhookCode(@VNewCreateProcessA);
    UnhookCode(@VNewCreateProcessW);
  end;
end;

end.

Für alle, die gerne mal sehen wollen, wie man dieses API-Hooking betreibt, habe ich ein einfaches Programm geschrieben. Mit RegRewrite ist es möglich, Einträge in der Registry durch andere Werte zu ersetzen. Sowohl die neuen Werte, als auch die Konfiguration des Programms werden in der Registry abgelegt ;-) . Guckt am besten in den Quelltext, wenn ihr wissen wollt, wie ihr das Programm richtig konfiguriert – es ist, wie gesagt, nur ein kleiner Test gewesen :) .

  RegRewrite (294.5 KiB, 40 hits)

Zum Schluss noch etwas Rechtliches:
Der Autor dieses Programms haftet nicht für Schäden an Soft- oder Hardware oder Vermögensschäden, die durch das Benutzen des Programms entstehen, es sei denn, diese beruhen auf einem grob fahrlässigen oder vorsätzlichen Handeln des Autors, seiner Erfüllungsgehilfen oder seiner gesetzlichen Vertreter.
Für Schäden an der Gesundheit, dem Körper oder dem Leben des Nutzers haftet der Autor uneingeschränkt.


Hookende Grüße, Kenny

12 Februar 2010 ~ 2 Comments | ÄHNLICHE ARTIKEL

[Update] MeaTLight: Nerven schonen an der Ampel

Autofahrer kennen es bestimmt – die Ampel schaltet nie so, wie es eigentlich sinnvoll wäre. Oft scheint es, als ob die Vorfahrtstraße länger grün hat, als die Seitenstraße – und das, obwohl die meisten Autos doch aus der Seitenstraße kommen. Vor allem im Berufsverkehr ist dieses unnötige Warten eine richtige Plage. Doch was kann man dagegen tun?

Genau über die Frage habe ich die letzten Tage nachgedacht und bin zu einer theoretischen Lösung gekommen: die MeaTLight (gesprochen “Meat Light”). Die Abkürzung steht für “Measuring Traffic Light” und macht auch genau das: Sie misst, wieviele Autos an der Ampel warten.

Sie misst? Sollte sie nicht lieber zählen?
Eigentlich schon, aber messen ist theoretisch einfacher als zählen. Um zählen zu können, muss man einzelne Fahrzeuge unterscheiden können – man muss die vorhandenen Gegebenheiten berücksichtigen, was einen Mehraufwand in der Entwicklung bedeutet. Zum messen kann man sich seinen eigenen Maßstab wählen. Man könnte z.B. eine Markierung auf der Fahrbahn anbringen, die Licht einer bestimmten Wellenlänge reflektiert. Wenn ein Auto auf der Fahrbahn steht, würde dieses die Markierung verdecken und ein etwaiger Sensor würde das erkennen können, da die das Licht nicht mehr kontrolliert reflektiert wird. Je nachdem, wie lang die Markierung ist, kann man messen, wie lang die wartende Autoschlange ist.

Der Rest ist dann relativ einfach: Während dem Umschalten misst die Ampel, wie viele Autos warten und berechnet daraus die Länge der Grünphase. Das kann problemlos im Wechsel geschehen, sodass die Fahrtrichtung, die im Moment wenig Verkehr verzeichnet auch weniger Zeit in Anspruch nimmt.

Jetzt mag der Einspruch kommen, dass doch längere Fahrzeuge (z.B. Busse und LKWs) eine längere Strecke verdecken und als mehrere Autos gewertet werden würden. Das mag in der Tat sein. Aber aus Erfahrung wissen wir alle, dass große Fahrzeuge auch entsprechend länger für das Anfahren benötigen, als kleine Fahrzeuge (es sei denn in dem kleinen Fahrzeug sitzt ein schlechter Autofahrer).

Nunja, die Idee ist natürlich nicht so ausgereift, wie zum Beispiel beim Projekt Light Traffic, aber ich wollte meine Idee einfach mal in die Welt hinaus posaunen. ;-)

Update:
Hihi! Kleine Notiz am Rande: Mein Artikel hier hat die Domain Meatlight.nl vom ersten Platz der Google-Suchergebnisse für “meatlight” verdrängt. Schon irgendwie lustig! :D

'meatlight' Suchergebnisse



Autofahrende Grüße, Kenny

11 Februar 2010 ~ 1 Comment | ÄHNLICHE ARTIKEL

Woolworth auf dem aufsteigenden Ast

Woolworth? Kennst du nicht? Dieser Krimskrams-Laden? Die mit dem roten Stuhl Logo? Ja, genau! Die, die Ende 2008 insolvent gegangen sind. Zumindest fast. Wie ich nun erfahren habe, hatte sich die deutsche Tochter neu firmiert und einen Teil der Läden übernommen.

Und das gelebte Konzept scheint wohl gut zu funktionieren, denn letztens habe ich ein Plakat gesehen, das einen neuen Woolworth (oder “Wullwutt”, wie meine Mama zu sagen pflegt) direkt hier in der Nähe ankündigt.

Woolworth-Ankündigung

Ich persönlich bin ja mal gespannt, wie lange der Laden hier überleben wird. Wir haben hier nämlich eine ziemlich hohe Anbieterfluktuation. Die Räume, in die Woolworth ziehen wird, sind derzeit noch von einem Ramschladen belegt, der auch erst vor ein paar Monaten dort eingezogen ist.

Solvente Grüße, Kenny

145
no-www.org extra-www.org

Datenbank: 45 Abfragen in 0.8790.879 Sekunden