/* * utils.c -- Various utility functions * * Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net> * Some of the ut_* functions are derived from Cabber debug/log code. * from_iso8601() comes from the Pidgin (libpurple) project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses/>. */#include<config.h>#include<stdio.h>#include<stdlib.h>#include<string.h>#include<stdarg.h>#include<errno.h>#ifdef HAVE_LIBIDN#include<idna.h>#include<stringprep.h>staticcharidnprep[1024];#endif#include<glib.h>#include<glib/gprintf.h>/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */#ifdef __CYGWIN__# define timezonevarexternlongtimezone;#endif#include<time.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<ctype.h>#include"utils.h"#include"logprint.h"#include"settings.h"#include"main.h"#include"screen.h"staticintDebugEnabled;staticchar*FName;// jidtodisp(jid)// Strips the resource part from the jid// The caller should g_free the result after use.char*jidtodisp(constchar*fjid){char*ptr;char*alias;if(!fjid){scr_LogPrint(LPRINT_LOGNORM,"** jidtodisp: NULL JID, ""this is probably a bug, please report!");returnNULL;}alias=g_strdup(fjid);if((ptr=strchr(alias,JID_RESOURCE_SEPARATOR))!=NULL){*ptr=0;}returnalias;}// The caller must free the string after use.char*jid_get_username(constchar*fjid){char*ptr;char*username;username=g_strdup(fjid);if((ptr=strchr(username,JID_DOMAIN_SEPARATOR))!=NULL){*ptr=0;}returnusername;}// The caller must free the string after use.char*get_servername(constchar*username,constchar*servername){char*ptr;char*server;if(!username){returnNULL;}if((ptr=strchr(username,JID_DOMAIN_SEPARATOR))!=NULL){server=g_strdup(ptr+1);returnserver;}returng_strdup(servername);}// The caller must free the string after use.char*compose_jid(constchar*username,constchar*servername,constchar*resource){char*fjid;if(!strchr(username,JID_DOMAIN_SEPARATOR)){fjid=g_strdup_printf("%s%c%s%c%s",username,JID_DOMAIN_SEPARATOR,servername,JID_RESOURCE_SEPARATOR,resource);}else{fjid=g_strdup_printf("%s%c%s",username,JID_RESOURCE_SEPARATOR,resource);}returnfjid;}gbooleanjid_equal(constchar*jid1,constchar*jid2){char*a,*b;intret;if(!jid1&&!jid2)returnTRUE;if(!jid1||!jid2)returnFALSE;a=jidtodisp(jid1);b=jidtodisp(jid2);ret=strcasecmp(a,b);g_free(a);g_free(b);return(ret==0)?TRUE:FALSE;}// expand_filename(filename)// Expand "~/" with the $HOME env. variable in a file name.// The caller must free the string after use.char*expand_filename(constchar*fname){if(!fname)returnNULL;if(!strncmp(fname,"~/",2)){char*homedir=getenv("HOME");if(homedir)returng_strdup_printf("%s%s",homedir,fname+1);}returng_strdup(fname);}#ifndef LOUDMOUTH_USES_SHA256// fingerprint_to_hex(fprstr, hex, fpr_len)// Convert the binary fingerprint fprstr (which is fpr_len bytes long)// to a NULL-terminated hexadecimal string hex.// The destination array hex should have been preallocated by the caller,// and should be big enough (i.e. >= 3*fpr_len bytes).voidfingerprint_to_hex(constchar*fprstr,char*hex,size_tfpr_len){unsignedinti;constunsignedchar*fpr=(constunsignedchar*)fprstr;char*p;hex[0]=0;if(!fpr||fpr_len<16)return;for(p=hex,i=0;i<fpr_len-1;i++,p+=3)g_snprintf(p,4,"%02X:",fpr[i]);g_snprintf(p,3,"%02X",fpr[i]);}// hex_to_fingerprint(hex, fpr, fpr_len)// Convert the hexadecimal fingerprint hex to a byte array fpr[].// The fpr array should have been preallocated with a size >= fpr_len.gbooleanhex_to_fingerprint(constchar*hex,char*fpr,size_tfpr_len){unsignedinti;constchar*p;if(fpr_len<16)returnFALSE;fpr[0]=0;if(strlen(hex)!=fpr_len*3-1)returnFALSE;for(i=0,p=hex;i<fpr_len&&*p&&*(p+1);i++,p+=3){// Check we have two hex digits followed by a colon (or end of string)if(!isxdigit(*p)||!isxdigit(*(p+1)))returnFALSE;if(*(p+2)&&(*(p+2)!=':'))returnFALSE;fpr[i]=(char)g_ascii_strtoull(p,NULL,16);}returnTRUE;}#endifstaticgbooleantracelog_create(void){FILE*fp;structstatbuf;interr;char*v;fp=fopen(FName,"a");if(!fp){scr_LogPrint(LPRINT_NORMAL,"ERROR: Cannot open tracelog file: %s",strerror(errno));returnFALSE;}err=fstat(fileno(fp),&buf);if(err||buf.st_uid!=geteuid()){fclose(fp);if(err)scr_LogPrint(LPRINT_NORMAL,"ERROR: cannot stat the tracelog file: %s",strerror(errno));elsescr_LogPrint(LPRINT_NORMAL,"ERROR: tracelog file does not belong to you!");returnFALSE;}if(fchmod(fileno(fp),S_IRUSR|S_IWUSR)){scr_LogPrint(LPRINT_NORMAL,"WARNING: Cannot set tracelog file permissions: %s",strerror(errno));}v=mcabber_version();fprintf(fp,"New trace log started. MCabber version %s\n""----------------------\n",v);g_free(v);fclose(fp);returnTRUE;}// The caller must free the string after use.staticgchar*tracelog_level_guard(constgchar*key,constgchar*new_value){intnew_level=0;if(new_value)new_level=atoi(new_value);if(DebugEnabled<1&&new_level>0&&FName&&!tracelog_create())DebugEnabled=0;elseDebugEnabled=new_level;returng_strdup(new_value);}// The caller must free the string after use.staticgchar*tracelog_file_guard(constgchar*key,constgchar*new_value){gchar*new_fname=NULL;if(new_value)new_fname=expand_filename(new_value);if(g_strcmp0(FName,new_fname)){g_free(FName);FName=new_fname;if(DebugEnabled>0&&!tracelog_create()){g_free(FName);FName=NULL;}}elseg_free(new_fname);returng_strdup(new_value);}// ut_init_debug()// Install option guards before initial config file parsing.voidut_init_debug(void){DebugEnabled=0;FName=NULL;settings_set_guard("tracelog_level",tracelog_level_guard);settings_set_guard("tracelog_file",tracelog_file_guard);}voidut_write_log(unsignedintflag,constchar*data){if(!DebugEnabled||!FName)return;if(((DebugEnabled>=2)&&(flag&(LPRINT_LOG|LPRINT_DEBUG)))||((DebugEnabled==1)&&(flag&LPRINT_LOG))){FILE*fp=fopen(FName,"a+");if(!fp){scr_LogPrint(LPRINT_NORMAL,"ERROR: Cannot open tracelog file: %s.",strerror(errno));return;}// Check file permissions again (it could be a new file)fchmod(fileno(fp),S_IRUSR|S_IWUSR);if(fputs(data,fp)==EOF)scr_LogPrint(LPRINT_NORMAL,"ERROR: Cannot write to tracelog file.");fclose(fp);}}// checkset_perm(name, setmode)// Check the permissions of the "name" file/dir// If setmode is true, correct the permissions if they are wrong// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correctintcheckset_perm(constchar*name,unsignedintsetmode){intfd;structstatbuf;if(!name)return-1;#ifdef __CYGWIN__// Permission checking isn't efficient on Cygwinreturn0;#endiffd=stat(name,&buf);if(fd==-1)return-1;if(buf.st_uid!=geteuid()){scr_LogPrint(LPRINT_LOGNORM,"Wrong file owner [%s]",name);return1;}if(buf.st_mode&(S_IRGRP|S_IWGRP|S_IXGRP)||buf.st_mode&(S_IROTH|S_IWOTH|S_IXOTH)){if(setmode){mode_tnewmode=0;scr_LogPrint(LPRINT_LOGNORM,"Bad permissions [%s]",name);if(S_ISDIR(buf.st_mode))newmode|=S_IXUSR;newmode|=S_IRUSR|S_IWUSR;if(chmod(name,newmode)){scr_LogPrint(LPRINT_LOGNORM,"WARNING: Failed to correct permissions!");return1;}scr_LogPrint(LPRINT_LOGNORM,"Permissions have been corrected");}else{scr_LogPrint(LPRINT_LOGNORM,"WARNING: Bad permissions [%s]",name);return1;}}return0;}constchar*ut_get_tmpdir(void){staticconstchar*tmpdir;constchar*tmpvars[]={"MCABBERTMPDIR","TMP","TMPDIR","TEMP"};unsignedinti;if(tmpdir)returntmpdir;for(i=0;i<(sizeof(tmpvars)/sizeof(constchar*));i++){tmpdir=getenv(tmpvars[i]);if(tmpdir&&tmpdir[0]&&tmpdir[0]=='/'&&tmpdir[1]){// Looks ok.returntmpdir;}}// Default temporary directorytmpdir="/tmp";returntmpdir;}// to_iso8601(dststr, timestamp)// Convert timestamp to iso8601 format, and store it in dststr.// NOTE: dststr should be at last 19 chars long.// Return should be 0intto_iso8601(char*dststr,time_ttimestamp){structtm*tm_time;intret;tm_time=gmtime(&timestamp);ret=snprintf(dststr,19,"%.4d%02d%02dT%02d:%02d:%02dZ",(int)(1900+tm_time->tm_year),tm_time->tm_mon+1,tm_time->tm_mday,tm_time->tm_hour,tm_time->tm_min,tm_time->tm_sec);return((ret==-1)?-1:0);}// from_iso8601(timestamp, utc)// This function came from the Pidgin project, gaim_str_to_time().// (Actually date may not be pure iso-8601)// Thanks, guys!// ** Modified by somian 10 Apr 2006 with advice from ysth.time_tfrom_iso8601(constchar*timestamp,intutc){structtmt;time_tretval=0;charbuf[32];char*c;inttzoff=0;inthms_succ=0;inttmpyear;time(&retval);localtime_r(&retval,&t);/* Reset time to midnight (00:00:00) */t.tm_hour=t.tm_min=t.tm_sec=0;snprintf(buf,sizeof(buf),"%s",timestamp);c=buf;/* 4 digit year */if(!sscanf(c,"%04d",&tmpyear))return0;t.tm_year=tmpyear;c+=4;if(*c=='-')c++;t.tm_year-=1900;/* 2 digit month */if(!sscanf(c,"%02d",&t.tm_mon))return0;c+=2;if(*c=='-')c++;t.tm_mon-=1;/* 2 digit day */if(!sscanf(c,"%02d",&t.tm_mday))return0;c+=2;if(*c=='T'||*c=='.'){/* we have more than a date, keep going */c++;/* skip the "T" *//* 2 digit hour */if(sscanf(c,"%02d:%02d:%02d",&t.tm_hour,&t.tm_min,&t.tm_sec)==3){hms_succ=1;c+=8;}elseif(sscanf(c,"%02d%02d%02d",&t.tm_hour,&t.tm_min,&t.tm_sec)==3){hms_succ=1;c+=6;}if(hms_succ){inttzhrs,tzmins;if(*c=='.')/* dealing with precision we don't care about */while(isdigit(*++c));if((*c=='+'||*c=='-')&&sscanf(c+1,"%02d:%02d",&tzhrs,&tzmins)){tzoff=tzhrs*60*60+tzmins*60;if(*c=='+')tzoff*=-1;}if(tzoff||utc){#ifdef HAVE_TM_GMTOFFtzoff+=t.tm_gmtoff;#else# ifdef HAVE_TIMEZONEtzset();/* making sure */tzoff-=timezone;# endif#endif}}}t.tm_isdst=-1;retval=mktime(&t);retval+=tzoff;returnretval;}/** * Derived from libjabber/jid.c, because the libjabber version is not * really convenient for our usage. * * Check if the full JID is valid * Return 0 if it is valid, non zero otherwise */intcheck_jid_syntax(constchar*fjid){constchar*str;constchar*domain,*resource;intdomlen;#ifdef HAVE_LIBIDNchar*idnpp;intr;#endifif(!fjid)return1;domain=strchr(fjid,JID_DOMAIN_SEPARATOR);/* the username is optional */if(!domain){domain=fjid;}else{/* node identifiers may not be longer than 1023 bytes */if((domain==fjid)||(domain-fjid>1023))return1;domain++;#ifdef HAVE_LIBIDNidnpp=idnprep;str=fjid;while(*str!=JID_DOMAIN_SEPARATOR)*idnpp++=*str++;*idnpp=0;r=stringprep(idnprep,1023,0,stringprep_xmpp_nodeprep);if(r!=STRINGPREP_OK||!idnprep[0])return1;/* the username looks okay */#else/* check for low and invalid ascii characters in the username */for(str=fjid;*str!=JID_DOMAIN_SEPARATOR;str++){if(*str<=' '||*str==':'||*str==JID_DOMAIN_SEPARATOR||*str=='<'||*str=='>'||*str=='\''||*str=='"'||*str=='&'){return1;}}/* the username is okay as far as we can tell without LIBIDN */#endif}resource=strchr(domain,JID_RESOURCE_SEPARATOR);/* the resource is optional */if(resource){domlen=resource-domain;resource++;/* resources may not be longer than 1023 bytes */if((*resource=='\0')||strlen(resource)>1023)return1;#ifdef HAVE_LIBIDNstrncpy(idnprep,resource,sizeof(idnprep));r=stringprep(idnprep,1023,0,stringprep_xmpp_resourceprep);if(r!=STRINGPREP_OK||!idnprep[0])return1;#endif}else{domlen=strlen(domain);}/* there must be a domain identifier */if(domlen==0)return1;/* and it must not be longer than 1023 bytes */if(domlen>1023)return1;/* /.+/ is not a valid domain name pattern */for(str=domain;*str&&*str!=JID_RESOURCE_SEPARATOR;str++)if(*str!='.')break;if(!*str||*str==JID_RESOURCE_SEPARATOR)return1;/* domain contains only dots */#ifdef HAVE_LIBIDNidnpp=idnprep;str=domain;while(*str!='\0'&&*str!=JID_RESOURCE_SEPARATOR)*idnpp++=*str++;*idnpp=0;r=stringprep_nameprep(idnprep,1023);if(r!=STRINGPREP_OK||!idnprep[0])return1;if(idna_to_ascii_8z(idnprep,&idnpp,IDNA_USE_STD3_ASCII_RULES)!=IDNA_SUCCESS)return1;elsefree(idnpp);#else/* make sure the hostname is valid characters */for(str=domain;*str!='\0'&&*str!=JID_RESOURCE_SEPARATOR;str++){if(!(isalnum(*str)||*str=='.'||*str=='-'||*str=='_'))return1;}#endif/* it's okay as far as we can tell */return0;}inlinevoidmc_strtolower(char*str){if(!str)return;for(;*str;str++)*str=tolower(*str);}// strip_arg_special_chars(string)// Remove quotes and backslashes before an escaped quote// Only quotes need a backslash// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b]voidstrip_arg_special_chars(char*s){intinstring=FALSE;intescape=FALSE;char*p,*t;if(!s)return;for(p=s;*p;p++){if(*p=='"'){if(!escape){instring=!instring;//memmove(p, p+1, strlen(p));for(t=p;*t;t++)*t=*(t+1);p--;}elseescape=FALSE;}elseif(*p=='\\'){if(!escape){//memmove(p, p+1, strlen(p));for(t=p;*t;t++)*t=*(t+1);p--;}escape=!escape;}elseescape=FALSE;}}// split_arg(arg, n, preservelast)// Split the string arg into a maximum of n pieces, taking care of// double quotes.// Return a null-terminated array of strings. This array should be freed// by the caller after use, for example with free_arg_lst().// If dontstriplast is true, the Nth argument isn't stripped (i.e. no// processing of quote chars)char**split_arg(constchar*arg,unsignedintn,intdontstriplast){char**arglst;constchar*p,*start,*end;unsignedinti=0;intinstring=FALSE;intescape=FALSE;arglst=g_new0(char*,n+1);if(!arg||!n)returnarglst;// Skip leading spacefor(start=arg;*start&&*start==' ';start++);// End of string pointerfor(end=start;*end;end++);// Skip trailing spacewhile(end>start+1&&*(end-1)==' ')end--;for(p=start;p<end;p++){if(*p=='"'&&!escape)instring=!instring;if(*p=='\\'&&!escape)escape=TRUE;elseif(escape)escape=FALSE;if(*p==' '&&!instring&&i+1<n){// end of parameter*(arglst+i)=g_strndup(start,p-start);strip_arg_special_chars(*(arglst+i));for(start=p+1;*start&&*start==' ';start++);p=start-1;i++;}}if(start<end){*(arglst+i)=g_strndup(start,end-start);if(!dontstriplast||i+1<n)strip_arg_special_chars(*(arglst+i));}returnarglst;}// free_arg_lst(arglst)// Free an array allocated by split_arg()voidfree_arg_lst(char**arglst){char**arg_elt;for(arg_elt=arglst;*arg_elt;arg_elt++)g_free(*arg_elt);g_free(arglst);}// replace_nl_with_dots(bufstr)// Replace '\n' with "(...)" (or with a NUL if the string is too short)voidreplace_nl_with_dots(char*bufstr){char*p=strchr(bufstr,'\n');if(p){if(strlen(p)>=5)strcpy(p,"(...)");else*p=0;}}// ut_expand_tabs(text)// Expand tabs and filter out some bad chars in string text.// If there is no tab and no bad chars in the string, a pointer to text// is returned (be careful _not_ to free the pointer in this case).// If there are some tabs or bad chars, a new string with expanded chars// and no bad chars is returned; this is up to the caller to free this// string after use.char*ut_expand_tabs(constchar*text){char*xtext,*linestart;char*p,*q;guintn=0,bc=0;if(!text)returnNULL;xtext=(char*)text;for(p=xtext;*p;p++)if(*p=='\t')n++;elseif(*p=='\x0d')bc++;// XXX Are there other special chars we should filter out?if(!n&&!bc)return(char*)text;xtext=g_new(char,strlen(text)+1+8*n);p=(char*)text;q=linestart=xtext;do{if(*p=='\t'){do{*q++=' ';}while((q-linestart)%8);}elseif(*p!='\x0d'){*q++=*p;if(*p=='\n')linestart=q;}}while(*p++);returnxtext;}// ut_unescape_tabs_cr(text)// Expand CR or TAB character sequences (\n, \t) in string text.// If there is no CR/TAB in text, then the original pointer is returned// (be careful _not_ to free the pointer in this case).// If there are some unescaped sequences, a new string with those chars// replaced with real newline/tab characters is allocated; in this case// this is up to the caller to free this string after use.char*ut_unescape_tabs_cr(constchar*text){char*xtext,*linestart;char*p,*q;if(!text)returnNULL;p=g_strstr_len(text,-1,"\\n");if(!p){p=g_strstr_len(text,-1,"\\t");if(!p)return(char*)text;}xtext=g_new(char,strlen(text)+1);p=(char*)text;q=linestart=xtext;do{if(*p=='\\'){if(*(p+1)=='\\'&&(*(p+2)=='n'||*(p+2)=='t')){// This is an escaped CR sequence*q++='\\';*q++='n';p+=2;continue;}if(*(p+1)=='n'||*(p+1)=='t'){// This is a CR sequencep++;*q++=(*p=='n'?'\n':'\t');continue;}}*q++=*p;}while(*p++);returnxtext;}/* Cygwin's newlib does not have strcasestr() *//* The author of the code before the endif is * Jeffrey Stedfast <fejj@ximian.com> * and this code is reusable in compliance with the GPL v2. -- somian */#if !defined(HAVE_STRCASESTR)# define lowercase(c) (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c))# define bm_index(c, icase) ((icase) ? lowercase (c) : (int) (c))# define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2))/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */# define bm_optimal(hlen, nlen) (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0)staticunsignedchar*__boyer_moore(constunsignedchar*haystack,size_thaystacklen,constunsignedchar*needle,size_tneedlelen,inticase){registerunsignedchar*hc_ptr,*nc_ptr;unsignedchar*he_ptr,*ne_ptr,*h_ptr;size_tskiptable[256],n;registerinti;#ifdef BOYER_MOORE_CHECKS/* we don't need to do these checks since memmem/strstr/etc do it already *//* if the haystack is shorter than the needle then we can't possibly match */if(haystacklen<needlelen)returnNULL;/* instant match if the pattern buffer is 0-length */if(needlelen==0)return(unsignedchar*)haystack;#endif /* BOYER_MOORE_CHECKS *//* set a pointer at the end of each string */ne_ptr=(unsignedchar*)needle+needlelen-1;he_ptr=(unsignedchar*)haystack+haystacklen-1;/* create our skip table */for(i=0;i<256;i++)skiptable[i]=needlelen;for(nc_ptr=(unsignedchar*)needle;nc_ptr<ne_ptr;nc_ptr++)skiptable[bm_index(*nc_ptr,icase)]=(size_t)(ne_ptr-nc_ptr);h_ptr=(unsignedchar*)haystack;while(haystacklen>=needlelen){hc_ptr=h_ptr+needlelen-1;/* set the haystack compare pointer */nc_ptr=ne_ptr;/* set the needle compare pointer *//* work our way backwards till they don't match */for(i=0;nc_ptr>(unsignedchar*)needle;nc_ptr--,hc_ptr--,i++)if(!bm_equal(*nc_ptr,*hc_ptr,icase))break;if(!bm_equal(*nc_ptr,*hc_ptr,icase)){n=skiptable[bm_index(*hc_ptr,icase)];if(n==needlelen&&i)if(bm_equal(*ne_ptr,((unsignedchar*)needle)[0],icase))n--;h_ptr+=n;haystacklen-=n;}elsereturn(unsignedchar*)h_ptr;}returnNULL;}/* * strcasestr: * @haystack: string to search * @needle: substring to search for * * Finds the first occurence of the substring @needle within the * string @haystack ignoring case. * * Returns a pointer to the beginning of the substring match within * @haystack, or NULL if the substring is not found. **/char*strcasestr(constchar*haystack,constchar*needle){registerunsignedchar*h,*n,*hc,*nc;size_tneedlelen;needlelen=strlen(needle);if(needlelen==0){return(char*)haystack;}elseif(bm_optimal(0,needlelen)){return(char*)__boyer_moore((constunsignedchar*)haystack,strlen(haystack),(constunsignedchar*)needle,needlelen,1);}h=(unsignedchar*)haystack;n=(unsignedchar*)needle;while(*(h+needlelen-1)){if(lowercase(*h)==lowercase(*n)){for(hc=h+1,nc=n+1;*hc&&*nc;hc++,nc++)if(lowercase(*hc)!=lowercase(*nc))break;if(!*nc)return(char*)h;}h++;}returnNULL;}#endif /* !HAVE_STRCASESTR */// startswith(str, word, ignore_case)// Returns TRUE if string str starts with word.intstartswith(constchar*str,constchar*word,guintignore_case){if(ignore_case&&!strncasecmp(str,word,strlen(word)))returnTRUE;elseif(!ignore_case&&!strncmp(str,word,strlen(word)))returnTRUE;returnFALSE;}// mkcmdstr(cmd) returns a pointer to a const string with the command// prefixed with COMMAND_CHAR.constchar*mkcmdstr(constchar*cmd){staticcharfcmd[INPUTLINE_LENGTH+1];fcmd[0]=COMMAND_CHAR;fcmd[1]=0;strncat(fcmd+1,cmd,INPUTLINE_LENGTH-1);returnfcmd;}/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */