יום רביעי, אפריל 15, 2015

וחייה היונה ביחד עם Lync

בשעה טובה ומצלחת קיבלתי גישה לשירות ה Lync  עבור המשתמש שלי , השירות שלי מסופק ע"י  Office365 ללא אחסון מקומי.

שם המשתמש שלי לחיבור עבור office365 הוא user@myerfm.blogspot.com והסיסמה היא secret.

 אצתי רצתי ליונה (pidgin) התקנתי את SIPE (יאפ על וינדוס) אז  :


לאחר מכן בטאב של מאפיינים מתקדמים יש למלא :

החלק החשוב זה ה UA אצלי עבד עם :
UCCAPI/15.0.4420.1017 OC/15.0.4420.1017 (Microsoft Lync)


מה לא עובד :

ביצוע שיחות קוליות (צד שני לא מקבל התראה על שיחה נכנסת).
העברת קבצים. 

מה כן עבד :
IM
OTR עושה קולות של עובד.
חיפוש והוספת משתמשים מאנשי הקשר ב office365
 

יום ראשון, אפריל 05, 2015

חוויות בתהליך מעבר ל CMAKE + c++11 שלום עולם

התמזל מזלי ומתנה נפלה בחלקי העברת קוד שמשתמש במגוון ספריות לשימוש ב C++11 יחסית ביחד עם CMake.

התהליך התעכב רבות והתחיל רק השבוע פה לכן אני חושב לרכז מספר הגיגים על התהליך.

אני מקווה לטוב ולכן נכון לעכשיו אני בודק רק על gcc אבל אני מקווה שהכל ילך חלק וזה גם יעבוד על VS 2013 איתו אני עובד יותר.

CMakeLists עבור שלום עולם יראה כך :

cmake_minimum_required(VERSION 2.6)
project(hello_world)
FIND_PACKAGE (Threads)


if (CMAKE_COMPILER_IS_GNUCXX)
  SET(CMAKE_CXX_FLAGS "-std=gnu++11") # for C++11 in gcc
endif()

add_executable(hello_world main.cpp)
target_link_libraries (hello_world ${CMAKE_THREAD_LIBS_INIT})


install(TARGETS hello_world RUNTIME DESTINATION bin)

הוספת המשתנה CMAKE_CXX_FLAGS פותרת את:


In file included from /usr/include/c++/4.9/thread:35:0,
                 from /opt/video/wasteoftime/cpp/threading/proj/hello_world/main.cpp:2:
/usr/include/c++/4.9/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 #error This file requires compiler and library support for the \
  ^ 


השימוש ב ${CMAKE_THREAD_LIBS_INIT} דרוש בשביל שניתן יהיה להשתמש בנימים , ועל הדרך פותר החריגה (שאין לי שמץ למה היא קוראת)
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error -1

כאשר משתמשים ב std::call_once (אני לא יודע אם זה באג או שאני פשוט מפספס משהוא בסיסי) ולא מתלנקקים מול pthread דוגמאת קוד לריסוק:

#include <iostream>
#include <mutex>

class resource
{
  std::once_flag resource_flag;
  void init_once(){}
public:
  resource(){};
  ~resource(){};
  void  doSomething()
  {
    std::call_once(resource_flag,&resource::init_once,this);
    return;
  }
};

int main()
{
  resource r;
  r.doSomething();
  return 0;
}

ככלל קוד שלום עולם נראה כך :

#include <iostream>
#include <thread>

static void f()
{
    std::cout << "Hello, world!" << std::endl;
}

int main(int argc, char **argv) {
  
    std::thread t (f);
    t.join();
    return 0;
} 
הפתעה כואבת היתה (כן כן אני יודע באג של מתחילים) כאשר העברתי ייחוס (reference) לאובייקט ארעי במקום לאובייקט עצמו (קוד שיעורי בית עם באג) :

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int accum = 0;

void square (int x , std::mutex & mutex) 
{
  std::lock_guard <std::mutex> guard( mutex);
  accum += x * x;
}

int main()
{
  std::vector < std::thread> threads;
  std::mutex mutex;
  for (int i = 1; i < 21 ; i ++ )
  {
     threads.push_back(std::thread (  square, i,mutex)) ; 
  }
  for (std::vector < std::thread>::iterator it = threads.begin();it != threads.end();++it)
  {
    it->join();
  }
  std::cout << accum << std::endl;
  return 0;
}

הנקודה הבעייתית במקרה של העברת ייחוס לאובייקט אירעי ולא לאובייקט עצמו:

     threads.push_back(std::thread (  square, i,mutex)) ; 
הקוד הנכון הוא :
     threads.push_back(std::thread (  square, i,std::ref (mutex) )) ; 

הופתעתי לטובה כי ניתן להשתמש די בקלות בהעברת חריגות כמעת בצורה זהה לבוסט קוד (קוד דוגמא) בבוסט :

#include <boost/exception/all.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

void avodashora()
{
  if (  rand () % 100 == 0)
  {
    throw boost::enable_current_exception(std::runtime_error("piturim"));
  }
}

class oved_cablan
{
public:
  void operator()(boost::exception_ptr & tzarot)  
  {
    try 
    {
      while (true)
      {
        avodashora();
      } 
    }
    catch (...)
    {
      tzarot = boost::current_exception();
    }
  }
};

class dor_aleph
{
public:
    dor_aleph(): 
    ben_dod_shel
    (
      boost::bind 
      (
        &oved_cablan::operator(),
        boost::ref(oved_pashut),
        boost::ref( tzarot ) 
      ) 
    )
    {
      //a dor_aleph , needs ben_dod_shel to the work for him
      //any problem that ben_dod_shel will get be refernced to oved_pashut
    }

    void work()
    {
      ben_dod_shel.join();//our dor aleph need to wait till ha ben dod will finish the work he assigned to

      if (tzarot)
      {
        boost::rethrow_exception( tzarot );
      }
    }
private:
    oved_cablan          oved_pashut;
    boost::thread        ben_dod_shel;
    boost::exception_ptr tzarot;
};

int main()
{
  try
  {
   dor_aleph a;
   a.work();
  }
  catch (std::exception & e)
  {
    std::cout << e.what() << std::endl;
  }
  return 0;
}
הקוד המומר לc++11

#include <boost/exception/all.hpp>
#include <iostream>
#include <thread>

void avodashora()
{
  if (  rand () % 100 == 0)
  {
    throw std::runtime_error("piturim");
  }
}

class oved_cablan
{
public:
  void operator()(std::exception_ptr & tzarot)  
  {
    try 
    {
      while (true)
      {
        avodashora();
      } 
    }
    catch (...)
    {
      tzarot = std::current_exception();
    }
  }
};

class dor_aleph
{
public:
    dor_aleph(): 
    ben_dod_shel(oved_pashut,std::ref(tzarot))
    {
      //a dor_aleph , needs ben_dod_shel to the work for him
      //any problem that ben_dod_shel will get be refernced to oved_pashut
    }

    void work()
    {
      ben_dod_shel.join();//our dor aleph need to wait till ha ben dod will finish the work he assigned to

      if (tzarot)
      {
        std::rethrow_exception( tzarot );
      }
    }
private:
    oved_cablan        oved_pashut;
    std::thread        ben_dod_shel;
    std::exception_ptr tzarot;
};

int main()
{
  try
  {
   dor_aleph a;
   a.work();
  }
  catch (std::exception & e)
  {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

יום שבת, מרץ 07, 2015

להתקין שירות LDAP על כוס תה

היה לי קצת זמן לשרוף אז החלטתי להרים שירות LDAP עבור הבדיקות שלי .בחרתי להשתמש ב slapd שיספק לי את שירותי ההזדהות.


השירות יותקן על 192.168.1.9 עבור הארגון example, נשתמש בשם הלא חוקי example.local
התקנה ראשונית מתחילה בחבילות :

apt-get install slapd ldap-utils

תשאלו להכניס סיסמה עבור המשתמש שינהל את הסביבה (האדמין) ותקבלו תוצאה דומה ל :

Setting up slapd (2.4.40-3) ...

  Creating new user openldap... done.

  Creating initial configuration... done.

  Creating LDAP directory... done.

Setting up ldap-utils (2.4.40-3) ...

Processing triggers for libc-bin (2.19-13) ...


בקשו ממנהל החבילות להגדיר את השרת :

dpkg-reconfigure -plow slapd

תשאלו האם אתם רוצים להתחיל מאפס (ללא שום בסיס נתונים (אני עצלן אז לא)).
תשאלו מהוא הדומיין שברשותכם  נכניס example.local.
עבור השאלה מהוא שם הארגון נמלא example
תתבקשו למלא את הסיסמא עבור המשתמש שינהל את הסביבה.
יש בחירה בין סוגי backend שונים ,בחרתי ב MDB (ברירת מחדל).
לא עבור הסרת בסיסי הנתונים
אישור להעביר את כל הקבצים עבור ההתקנה
מנעתי שימוש ב LDAP2

פתחתי את /etc/defaults/slapd ושיניתי שיאזין רק מקומית :
SLAPD_SERVICES="ldap://127.0.0.1:389/ ldapi:///"


ניצור קובץ בשם access.ldif שיאפשר הרשאות :

dn: olcDatabase={1}hdb,cn=config

changetype: modify

add: olcAccess

olcAccess: {1}to attrs=loginShell,gecos

  by dn="cn=admin,dc=example,dc=local" write

  by self write

  by * read


נטען את הקובץ למערכת ע"י

ldapmodify -Y EXTERNAL -H ldapi:/// -f ./access.ldif

אנחנו מייצרים היררכיה , המשתמשים יהיו מתחת ל People,dc=example,dc=local

יצרת  קובץ People.ldif עם התוכן :
dn: ou=people,dc=example,dc=local

ou: people

description: All people in Open Solutions

objectclass: organizationalUnit


טעינת הקובץ:

ldapadd -x -D cn=admin,dc=example,dc=local -W -f ./People.ldif


יצירת משתמש  :
dn: cn=joeb,ou=People,dc=example,dc=local
objectClass: inetOrgPerson
uid: joeb
sn: Bloggs
givenName: Joe
cn: Joe Bloggs
displayName: Joe Bloggs
userPassword: pwdpwdpwd
mail: joeb@example.local
o: example
title: user
ou: second floor
בשביל למחוק את המשתמש שנוצר :

ldapdelete -x -W -D "cn=admin,dc=example,dc=local"  "cn=joeb,ou=People,dc=example,dc=local"

יום חמישי, מרץ 05, 2015

קוד בדיקה מול term.ie

אתמול הייתי צריך לבדוק מערכת שמתקשרת ע"י שימוש ב rest מול שרת בדיקה, להפתעתי גיליתי ששירות ההזדהות עבר להשתמש ב oauth  במקום שירותי ההזדהות על בסיס זיהוי NTLM.

גיגול מהיר נתן לי את liboauthcpp מכיוון שלא יכלתי להתקין אותו הייתי צריך לעבוד קצת עקום - בניתי מקומית את הספרייה, לאחר מכן בשביל לבדוק התלנקקתי מול boost  וliboauthcpp עם מסלול מדוייק.
שמתי את ספריית ההזדהות ב /opt/oauth/libauthcpp

mkdir -p /opt/oauth/myauth/src
cp -r ~/myauth/* /opt/oauth/myauth/src/

בתוכה יצרתי קובץ CMakeFiles.txt:


cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 

find_package(Boost 1.49 COMPONENTS system filesystem REQUIRED)

IF(NOT MYAUTH_TOP_LEVEL)
        SET(MYAUTH_TOP_LEVEL ${CMAKE_CURRENT_SOURCE_DIR}/)
endif()
GET_FILENAME_COMPONENT(MYAUTH_TOP_LEVEL ${MYAUTH_TOP_LEVEL} ABSOLUTE)
SET (MYAUTHCPP_SRC ${MYAUTH_TOP_LEVEL}/src)

add_executable (myauth 
                ${MYAUTHCPP_SRC}/main.cc)

include_directories(myauth /opt/oauth/liboauthcpp/include/)
target_link_libraries(
        myauth /opt/oauth/liboauthcpp/build/liboauthcpp.a
        boost_system
        pthread
       ) 

אני צריך את בוסט כי הקוד שלי כבר משתמש בו, לצערי שרפתי קצת זמן על שגיאת כתיב בשם של הספרייה b קטנה לעומת B גדולה השורה השגוייה :

Find_package(boost 1.49 COMPONENTS system filesystem REQUIRED)


 יצרה את :

CMake Error at CMakeLists.txt:4 (find_package):
  By not providing "Findboost.cmake" in CMAKE_MODULE_PATH this project has
  asked CMake to find a package configuration file provided by "boost", but
  CMake did not find one.

  Could not find a package configuration file provided by "boost" (requested
  version 1.49) with any of the following names:

    boostConfig.cmake
    boost-config.cmake

התוכן של src/main.cc (מתוך liboauthcpp עם מספר שינויים קטנים שלי)  בשביל לבדוק הזדהות מול term.ie :

/**
Copyright (c) 2011 Stanford University (liboauthcpp)
Copyright (C) 2011 by swatkat (swatkat.thinkdigitATgmailDOTcom) (libtwitcurl)
 
 Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/

#include <iostream>
#include <liboauthcpp/liboauthcpp.h>
#include <boost/asio.hpp>

const std::string oauth_consumer_key = "key";            // Key from term.ie
const std::string oauth_consumer_secret = "secret";      // Secret from term.ie
const std::string oauth_server = "term.ie";
const std::string oauth_request_token_path ="/oauth/example/request_token.php";
const std::string oauth_access_token_path = "/oauth/example/access_token.php";
const std::string oauth_protected_path = "/oauth/example/echo_api.php";  //need to perform oauth before accessing
const std::string oauth_protected_resource_params = "method=foo&bar=baz";

//get http message body from server/path?query_string
//return http code , can throw
unsigned int getHttpMessageBody(
        const std::string & request_server,
     const std::string & request_path, 
        const std::string & query_string,
     std::string & message_body);

int main (int argc , char ** argv)
{
   if (argc > 1 && std::string(argv[1]) == std::string("--debug"))
   {
     OAuth::SetLogLevel(OAuth::LogLevelDebug);
   }
   
   OAuth::Consumer consumer(oauth_consumer_key, oauth_consumer_secret);
   OAuth::Client   oauth(&consumer);
           
   std::string request_token_url = "http://" + oauth_server + oauth_request_token_path;
   std::string request_token_query = oauth.getURLQueryString( OAuth::Http::Get, request_token_url);
   std::string request_tokenresp;
 
   if (200 != getHttpMessageBody(oauth_server,oauth_request_token_path,request_token_query,request_tokenresp))
   {
      return 1;
   }
   //Get access token
   OAuth::Token request_token = OAuth::Token::extract( request_tokenresp );
   oauth = OAuth::Client(&consumer, &request_token);

   std::string access_token_url = "http://" + oauth_server + oauth_access_token_path; 
   std::string access_token_query = oauth.getURLQueryString( OAuth::Http::Get, access_token_url, std::string( "" ), true );
   std::string access_token_resp ;

   if (200 != getHttpMessageBody(oauth_server,oauth_access_token_path,access_token_query,access_token_resp))
   {
      return 1;
   }

   OAuth::KeyValuePairs access_token_resp_data = OAuth::ParseKeyValuePairs(access_token_resp);
   OAuth::Token access_token = OAuth::Token::extract( access_token_resp_data );
   //Access token protected resource : 
   OAuth::Client protected_token(&consumer, &access_token);
   std::string oauth_protected_resource =   "http://" + oauth_server + oauth_protected_path;
   std::string protected_resource_query =   protected_token.getURLQueryString(OAuth::Http::Get, oauth_protected_resource + "?" + oauth_protected_resource_params);

   std::string protected_resp ;
   if (200 != getHttpMessageBody(oauth_server,oauth_protected_path,protected_resource_query,protected_resp))
   {
      return 1;
   }
   
   std::cout << protected_resp << std::endl;
                    
   return 0;
}

unsigned int getHttpMessageBody(
        const std::string & request_server,
     const std::string & request_path, 
        const std::string & query_string,
     std::string & message_body)
{
   boost::asio::ip::tcp::iostream stream;
   stream.connect (request_server, "http");
   if (! stream)
   {
     std::cout << " failed to connect to " << request_server << " " << stream.error().message() << std::endl;
     return 500;
   }

   stream << "GET " << request_path <<  "?" << query_string << " HTTP/1.0\r\n"
          << "Host: " <<  request_server << "\r\n"
          << "Accept: */*\r\n"
          << "Connection: close\r\n\r\n";

   stream.flush();
   std::string http_version;
   unsigned int status_code;

   stream >> http_version;
   stream >> status_code;
 
   if (!stream || http_version.substr(0, 5) != "HTTP/")
   {
      return 400;
   }
   if (status_code != 200)
   {
      return status_code;
   }

   std::string header;
   while (std::getline(stream, header) && header != "\r")//skip all headers
   {
   }
   //grab the conent of rdbuf
   std::ostringstream ss;
   ss << stream.rdbuf();
   message_body = ss.str();
   return status_code;
}

יום ראשון, מרץ 01, 2015

תודה לשלומי

הייתי צריך לנקות קצת את הראש היום בגלל באג שאני עובד עליו, ואז וראיתי הודעה על פרוייקט אויילר ב פלאנט.

מי שלא מכיר את פרוייקט אויילר , זה אחד המקומות היותר טובים שאני מכיר לנסות דברים תוך כדי לימוד.

ההצעה שלי לפתרון השאלה איך לעשות אופטימיזציה לחישוב מספר ראשוני היא שימוש ב composition (אבל זה בא על חשבון הקצעה) :

 
#include <stdio.h>
#include <time .h>
#include <string.h>

#define mMO_NUM_LIMIT (150000000)

static char mo_tabOfPrimes[mMO_NUM_LIMIT];

void mark_numbers_as_primes()
{
   int primeIndex;
   int compositionIndex;
   int maxNumberOfElemenentsInTab;
   memset(mo_tabOfPrimes,0,sizeof(mo_tabOfPrimes));
   maxNumberOfElemenentsInTab = sizeof(mo_tabOfPrimes)/sizeof(mo_tabOfPrimes[0]);

   for (primeIndex = 2 ;primeIndex < maxNumberOfElemenentsInTab;primeIndex ++ )
   {
     if (0 != mo_tabOfPrimes[primeIndex])
     {
       continue; //any number previously markes as a composition can no be prime
     }
     //any next value will be a composition of that code
     for (compositionIndex = primeIndex + primeIndex; compositionIndex < maxNumberOfElemenentsInTab;compositionIndex = compositionIndex + primeIndex)
     {
       mo_tabOfPrimes[compositionIndex] = 1;
     }
   }
}

int is_prime(const long indexToCheck)
{
  int check ;
  if (indexToCheck > mMO_NUM_LIMIT)
  {
    return 0;
  }
  return   (0 ==  mo_tabOfPrimes[indexToCheck]);
}

void printFirstNPrimes(int countToPrint)
{
  long i;
  int numberOfPrimesPrintedSoFar = 0;

  for (i = 0 ;
      ( i < mMO_NUM_LIMIT) &&( numberOfPrimesPrintedSoFar < countToPrint);
      i++)
  {
    if (1 == is_prime(i))
    {
       numberOfPrimesPrintedSoFar ++;
       printf("%d/%d %d is a prime number\n",numberOfPrimesPrintedSoFar,countToPrint,i);
    }
  }
}
int main()
{
   clock_t start;
   clock_t end; 
   double time_spent;

   start = clock();
   mark_numbers_as_primes();
   end = clock();
   time_spent = (end - start) / CLOCKS_PER_SEC;
   printf("spent %lf seconds when filling %lld prime numbers\n",time_spent,mMO_NUM_LIMIT);
   printFirstNPrimes(100);
   return 0;
}

תקלות תקלות תקלות

במהלך ההתקנה של ענני נתקלתי במספר שגיאות הגדרה, לכן אני מפרסם את אותם ואת הפתרונות שלהם.
קצת על המערכת  - את הענן יצרתי על גבי רסברי פיי שמספק שירותי שליחת דוא"ל בתוך exim4 קבלת דוא"ל ע"י שימוש ב dovcat , הזדהות , אנשי קשר ויומן, שיתוף קבצים, שירותי שמות ע"י bind9.

בחרתי ב exim4 ולא ב postfix סתם בגלל העדפה אישית באותו הרגע (לצרכים שלי שניהם עובדים) , הגדרתי שעבור עם smarthost פר משתמש :

צרות עם bind9 : 

במהלך ההגדרה שמתי לב שבקשות לא עוברות הלאה לשרת הבא : אני מספק X שמות בתוך הדומיין שלי , אבל אני צריך ששאר השמות מדומיינים אחרים יסופקו ע"י שרתים אחרים (לדוגמה ע"י הנתב הבא במקרה שלי 192.168.1.1) .

כאשר השתמשתי פשוט ב forwarders זה פשוט לא היה מעביר הלאה את הבקשות (ללא שום דבר מועיל ב לוגים). לאחר חפירה מצאתי שהבעיה היא בהרשאות הבקשה :


הפתרון :
        dnssec-validation no;(במקום auto). 


צרות עם דוא"ל:

503 AUTH command used when not advertised


בהגדרה של exim4 טעיתי ושמתי כוכבית עבור מי עושים relay, מה שצריך לעשות זה להגדיר את dc_relay_domains לערך ריק.

צרות עם openvpn כאשר יש מכשיר אנדרויד ללא רוט

אחד המכשירים שמתחבר לענני הוא מכשיר שלא עבר rooting מה שאומר כי אני נאלץ להשתמש בלקוח openvpn ללא יכולת tap.

אז ישבתי והגדרתי במשך כ 5 דקות שלמות שרת openvpn כמו בדר"כ רק ע"י החלפה לשימוש ב tun במקום tap וההגדרה הראשונית נראתה כך :


port 1194
proto tcp
dev tun
ca   /etc/openvpn/easy-rsa/keys/ca.crt
cert /etc/openvpn/easy-rsa/keys/server.crt
crl-verify /etc/openvpn/easy-rsa/keys/crl.pem
dh /etc/openvpn/easy-rsa/keys/dh1024.pem

server 192.168.0.0 255.255.255.0
push "route 192.168.1.0 255.255.255.0"
push "dhcp-option DNS 192.168.0.1"

client-to-client
keepalive 10 120
comp-lzo
persist-key
persist-tun
status openvpn-status.log
verb 4
mute 20
script-security 2

צורת העבודה הזאת מאפשרת dhcp ע"ג tun ועובדת בדביאן ווינדוס, כאשר ניסיתי להשתמש בהגדרות צד לקוח קיבלתי הודעה קריפטית לגבי dresses are not in the same /30 subnet (topology net30 מה שלכעצמו מוזר כי הכתובת שצריכה אמורה לעבור דרך dhcp. כמה שחיפשתי לא הצלחתי למצוא פתרון פרט ללספק כתובת (מאותו המרחב באמצעות שימוש בcient-config-dir ושם הגדרה של כתובת קבועה עבורו).

port 1194
proto tcp
dev tun
ca   /etc/openvpn/easy-rsa/keys/ca.crt
cert /etc/openvpn/easy-rsa/keys/server.crt
crl-verify /etc/openvpn/easy-rsa/keys/crl.pem
dh /etc/openvpn/easy-rsa/keys/dh1024.pem

server 192.168.0.0 255.255.255.0
push "route 192.168.1.0 255.255.255.0"
push "dhcp-option DNS 192.168.0.1"

client-config-dir ccd
route 192.168.0.0 255.255.255.252

client-to-client
keepalive 10 120
comp-lzo
persist-key
persist-tun
status openvpn-status.log
verb 4
mute 20
script-security 2

לאחר מכאן יש ליצור תיקיית ccd ובתוכה שם כמו שהשתמשת בעת ייצרת certificate עבור הלקוח.