ABAP log

May 19, 2007

Creating and changing a production order from ABAP.

Filed under: ABAP, SAP — abaplog @ 9:21 pm

Creating production orders is one of common tasks when you have some external system that does the planning. Even if you work “directly” in R/3, you will probably need to create or change production orders. The SAP’s standard answer – BDC – is not really performant if you are processing a lot of data. To disappointment of any ABAPer, SAP provides no BAPIs to deal with that task (they do for order confirmations though). But fortunately, there are some other function modules that can help us. Before going further, I have to say that those functions can help you to create a production order, change its header parameters like scheduled start, or change its operations. Changing components, documents or production resources still has to be done with BDC (CO01 or CO02).

For production orders, SAP has implemented an interface for external planning systems (and even provided some documentation!). Their own “external” system, APO, does not use those functions (actually, one function supporting different tasks), but they are kind of official and you can find some documents on them in SAP help. Of course, the use is not limited to external systems and the function can be used directly from ABAP programs.

So, let’s start with creating an order. The function that we are going to use is CLOI_CHANGES_UPL_31. Don’t get confused by the “31″ in its name. It works perfectly in 4.6 and (most likely) in later version of R/3. There is one inportant point that you have to keep in mind when working with the function: you are given just one call per session. That means, you can create one order or many orders, but with one call. After that, some internal state data is messed up and to save us from initializing it (which has to be done with some really internal functions), we better start a new sesion if we need to call the function again. So, in my example code below I am going to check whether the current session is not the last one allowed for the current user and call the function in a new task. Note that we have to do the check before EACH call unless we are really sure (I do it only once in the sample code below). Since it’s going to be an anynchronous call, we need to suspend our program until the function running in parallel finishes and get the return data into our program. This is achieved with standard SAP constructs like “performing … on end of task” in the CALL FUNCTION statement and “receive results” in the form that is performed. I am also using a global variable that tells me that the call is over. You can find more useful information in SAP OSS note 545860 that described those specifics about calling that function.

* 1. Create a production order.

data: t_cloi_ord_exp          type standard table of cloiord,
      lw_cloi_ord_exp         type cloiord,
      lt_cloi_msg_obj_log_exp type standard table of cloimoblog,
      lt_cloi_methods_exp     type standard table of cloimetlog,
      t_cloi_messages_exp     type standard table of cloimsglog,
      lw_cloi_messages_exp    type cloimsglog,
      ls_cloi_if_par_v2       like cloiifpar.

data:     lt_cloi_ordu        type standard table of cloiordu,
          lw_cloi_ordu        type cloiordu,
          lt_cloi_ordi        type standard table of cloiordi,
          lw_cloi_ordi        type cloiordi,
          lt_cloi_ord_opru    type standard table of CLOIOPERU,
          lw_cloi_ord_opru    type CLOIOPERU.

data:
  lf_date   like sy-datum,
  lf_date2  like sy-datum,
  f_flag(1) type c,
  f_aufnr   like aufk-aufnr.

data: f_act_sess like sm04dic-counter,
      f_max_sess like sm04dic-counter.

lf_date = sy-datum + 7.   "let's try to schedule it next week
lf_date2 = sy-datum + 14.

clear: ls_cloi_if_par_v2.

* those fields below are described in SAP help, but nevermind...
ls_cloi_if_par_v2-commitflg   = 'C'.
ls_cloi_if_par_v2-r3_version  = '40'.
ls_cloi_if_par_v2-metlog_req  = 'X'.
ls_cloi_if_par_v2-msglog_req  = 'X'.
ls_cloi_if_par_v2-msgobj_req  = 'X'.
ls_cloi_if_par_v2-ord_req     = 'X'.
ls_cloi_if_par_v2-ordseq_req  = 'X'.
ls_cloi_if_par_v2-ordopr_req  = 'X'.

* in case if we have external number range,
* we have to provide the future order number
lw_cloi_ordi-extaufnr = '1234567890'.

* material
lw_cloi_ordi-field =  'MATNR'.
lw_cloi_ordi-value =  'MATNUM1'.
append lw_cloi_ordi to lt_cloi_ordi.

* plant
lw_cloi_ordi-field =  'WERKS'.
lw_cloi_ordi-value = 'W001'.
append lw_cloi_ordi to lt_cloi_ordi.

* order type
lw_cloi_ordi-field =  'AUART'.
lw_cloi_ordi-value = 'PP01'.
append lw_cloi_ordi to lt_cloi_ordi.

* quantity
lw_cloi_ordi-field =  'BDMNG'.
lw_cloi_ordi-value = '100'.
append lw_cloi_ordi to lt_cloi_ordi.

lw_cloi_ordi-field =  'GLTRP'.    "finish date
lw_cloi_ordi-value = lf_date2.
append lw_cloi_ordi to lt_cloi_ordi.

lw_cloi_ordi-field =  'GSTRP'.    "start date
lw_cloi_ordi-value = lf_date.
append lw_cloi_ordi to lt_cloi_ordi.

* now check if we have one free session
call function 'TH_USER_INFO'
     importing
          act_sessions = f_act_sess
          max_sessions = f_max_sess
     exceptions
          others       = 1.

if sy-subrc  0 or f_act_sess >= f_max_sess.
  message e208(00) with 'No more free sessions'.
endif.

* and finally, let it happen!
clear: f_flag.
call function 'CLOI_CHANGES_UPL_31'
     starting new task 'CLOI_TEST'
     performing receive_res on end of task
     exporting
          cloi_if_par          = ls_cloi_if_par_v2
     tables
          cloi_ordi_imp        = lt_cloi_ordi
          cloi_method_log_exp  = lt_cloi_methods_exp
          cloi_message_log_exp = t_cloi_messages_exp
          cloi_msg_obj_log_exp = lt_cloi_msg_obj_log_exp
          cloi_ord_exp         = t_cloi_ord_exp.

* wait till the global variable will be set by our form
wait until f_flag = 'X'.

This is the form that is used to receive results and signalize
the end of processing:

form receive_res using taskname.

  data: lt_cloi_msg_obj_log_exp type standard table of cloimoblog,
        lt_cloi_methods_exp     type standard table of cloimetlog.

  receive results from function 'CLOI_CHANGES_UPL_31'
     tables
       cloi_method_log_exp        = lt_cloi_methods_exp
       cloi_message_log_exp       = t_cloi_messages_exp
       cloi_msg_obj_log_exp       = lt_cloi_msg_obj_log_exp
       cloi_ord_exp               = t_cloi_ord_exp.

  f_flag = 'X'.

endform.

The list of created production orders is returned back in the global table t_cloi_ord_exp. Now, let’s release them. (Setting, for example, technically complete status is similar). We are going to use the parameter cloi_ordu_imp instead of cloi_ordi_imp (I for insert, U for update). We populate the field FIELD with the value “METHOD” to say that we are not setting any values but we want to call a “method” of releasing the order (method in the same sense as in object programming).

* release created prod order

clear: lt_cloi_ordu.
loop at t_cloi_ord_exp into lw_cloi_ord_exp.
  check not lw_cloi_ord_exp-aufnr is initial.
  clear: lw_cloi_ordu.
  lw_cloi_ordu-aufnr  =  lw_cloi_ord_exp-aufnr.
  f_aufnr  =  lw_cloi_ord_exp-aufnr.
*   Methods:
*     SetTechnicalComplete
*     RevokeTechnicalCompletion
*     Schedule
*     Release
*     SetUserStatus
*     RevokeUserStatus
  lw_cloi_ordu-field  =  'METHOD'.
  lw_cloi_ordu-value  =  'Release'.
  append lw_cloi_ordu to lt_cloi_ordu.
endloop.

clear: lt_cloi_methods_exp, t_cloi_messages_exp,
       lt_cloi_msg_obj_log_exp, t_cloi_ord_exp.

clear: f_flag.
call function 'CLOI_CHANGES_UPL_31'
     starting new task 'CLOI_TEST'
     performing receive_res on end of task
     exporting
          cloi_if_par          = ls_cloi_if_par_v2
     tables
          cloi_ordu_imp        = lt_cloi_ordu
          cloi_method_log_exp  = lt_cloi_methods_exp
          cloi_message_log_exp = t_cloi_messages_exp
          cloi_msg_obj_log_exp = lt_cloi_msg_obj_log_exp
          cloi_ord_exp         = t_cloi_ord_exp.

wait until f_flag = 'X'.

Now, as the order is created and released, let’s do some simple change. We are going to change the workcenter of the order’s operation.

* change the work center of the operation 0010

clear: lt_cloi_ord_opru.
clear: lw_cloi_ord_opru.
lw_cloi_ord_opru-aufnr  =  f_aufnr.
lw_cloi_ord_opru-aplfl  =  0.
lw_cloi_ord_opru-vornr  =  '0010'.
lw_cloi_ord_opru-field  =  'ARBID'.
* note that this is the internal ID of workcenter!
lw_cloi_ord_opru-value  =  '10000001'.
append lw_cloi_ord_opru to lt_cloi_ord_opru.

clear: lt_cloi_methods_exp, t_cloi_messages_exp,
       lt_cloi_msg_obj_log_exp, t_cloi_ord_exp.

clear: f_flag.
call function 'CLOI_CHANGES_UPL_31'
     starting new task 'CLOI_TEST'
     performing receive_res on end of task
     exporting
          cloi_if_par          = ls_cloi_if_par_v2
     tables
          cloi_ord_opru_imp    = lt_cloi_ord_opru
          cloi_method_log_exp  = lt_cloi_methods_exp
          cloi_message_log_exp = t_cloi_messages_exp
          cloi_msg_obj_log_exp = lt_cloi_msg_obj_log_exp
          cloi_ord_exp         = t_cloi_ord_exp.

wait until f_flag = 'X'.

That’s it! The function that we use doesn’t let us do anything we may need, but it can be very useful anyways. It’s stable and “blessed” by SAP, and I have seen it used in third-party products that need to be integrated with SAP. If you have any doubts, or your supervisor does, don’t forget to point to the SAP help documentation. It’s a small reassurance that it’s not one of those “internal only” functions that are better to avoid. And, the main point will of course be the performance if compared with BDC.

May 7, 2007

Secure SAP?

Filed under: ABAP, CIF, SAP, security — abaplog @ 8:03 pm

 

After doing all kind of ABAP work for many years, it’s often amusing for me to see such a system as SAP R/3 being so vulnerable and so unprotected from attacks and sabotage. The system has very strong security mechanisms, so complicated that it probably takes years to build up the experience managing all that, but those things can be used to restrict access for normal users. Anyone who wants to target it can find some ways, combining existing functionality, holes not closed by administrators, probably complemented by social engineering. It seems that it’s just because normally the people that have an access to such system are too busy to screw it. But what if some nice behaving well paid consultant is actually looking to sell your data? Or if someone unhappy with the very wise executive decision to let a crowd of folks go look for another job wants to say the “last word”? The fact is, and I like to repeat it every time I hear that someone has decided to strip us of some authorisations, that anyone with some technical SAP experience will always find the way. Or let’s say, “most likely” will.

Before I go further, let me give a couple of notices:

Some things discussed below were actually tried by me, on various versions of SAP. It can happen that some trick I tried several years ago is already blocked by SAP. I never want to try certain tricks again – it’s not good, and I don’t have my own SAP system to play with. I assume no responsibility on the results of trying those innocent tricks.

The first thing that you learn as an ABAPer is that you can read the data directly from SAP database tables. And not only read, but also update. Direct updates, unless done on customer’s own tables, in theory can break the SAP warranty. But we have to do this from time to time, and there is nothing more simple that can screw up the whole system. Just one line of code. But hey, it’s not only about sabotage! What about messing with the accounting? Or changing your own user account information? During the SAP boom days (end of 90s), a code to get yourself the best authorisation available in the system was circulating over the Net. Simple updates of USR* tables. The guy who wrote it pulled it back later because he didn’t feel well about it.

Most companies have a policy of not allowing direct updates of SAP tables, but not every company enforces it through QA process. I remember how did I learn that direct updates are not good. It was probably a couple of months after I took my BC400 course that I got a task of programming a mass update of customer master. I think it was that simple update of KNVV that got my change back from QA with a big red cross. Good work! That’s when I understood also how important the quality assurance is.

The next funny thing you learn as an ABAP programmer is that there are some “system variables”. And also that they can be manipulated too. What is especially useful it’s changing the value of return code SY-SUBRC after failed authorisation check. Yes, you need to be authorised to change values in debugger, but this is what we all have in development systems, and for some limited periods even in production. No big deal if it’s dev? But it can be a good starting point and a gate to other systems! And it brings even more fun if when doing some nasty update, you change the “current user ID” to be stored as “who has updated it” to some other than yours!

Though at some critical points SAP checks the authorisation other than with standard authority-check and SY-SUBRC, most of time it’s still good old return code, and it doesn’t look like it’s going to change. But why should we ABAPers care if under the debugger, we can change almost any SAP program without asking SAP or our admin for access key. Just jump over it! (Kind of open-source – feel free to modify if you need to!) Then, why not to transport this change to the productive system if management thinks that having QA folks go through each modified piece of code that is going to be transported is a waste of corporate money? As if it’s not the code that runs the enterprise blood through the servers.

But it’s not only the code. There is a useful transaction SE16N, where SAP has implemented a command (“&sap_edit”) that allows you to modify just any table in SAP. The command name implies that it’s “internal use only” and should be reserved for SAP support folks, but I have never seen it asking whether I am an SAP employee. Oh, there are “change documents” that they use to track all those changes, but there is also a menu item to delete them, and after all, why not to play with the table storing them directly? OK, to do all those things you need to be authorised, but don’t give up: there are debugger (see above) and other tricks (see below) that get us there.

That was about changing the data, but sometimes even reading can be considered as a biggest sin. At some point in SAP career, everyone will see “authorisation hysteria”. It starts with some global effort to strip us of our rights. Sure, we developers should be hanged after seeing some secret production data, so to prevent the mass executions, they first revoke the rights for standard “business” transactions. Then, normally several years after that, someone says hey! those potentially bad bad guys have SE16 and can view data just from any table! Oh my! Now we set the authorisations for each table. S_TABU_DIS is the cure! And the tables are gone for us, forever. No, wait! we have database views! and many other things that you get using where-used-list for the table in question in SE11 (a “very special” SAP feature nearly unknown to professional accountants). There is even more. There are hundreds of funcion modules in SAP and the changes always are that after investing some time into research, but still considerably less than we spend explaining that if we are supposed to analyse production problems, we need some more authorisation to do that, we can find some magic function module that, being called directly in SE37, gives us the data we need. And don’t forget that very often developers are given rights to schedule background jobs that run with any user they want and then read the output. No limits – it can be helped only by a different authorisation process, where everything in a system is really integrated. For example, database views inherit authorisation of tables, and SELECTs check things like S_TABU_DIS authomatically. But overhead of doing that will probably make the systems unusable.

As so many things can be done if we got enough authorisations, one would ask “what’s the big deal”? Just stop giving the debug with change authorisation away and this will save the system from abuse. Well, there are always countermeasures. Each system has some special users that have special authorisations. And frequently those users are maintained as default login user for some RFC connection. One very well known example is the user of the APO CIF interface. Till mid-2006, it was a general advice from SAP to give that interface user SAP_ALL authorisation. Now, years after introducing CIF, they have issued an OSS note that describes a safer setup. But the system that still has the old setup, or has some other user for interfacing with other systems, there are fancy things one can do with that. You need just a debug session without change authorisation and some RFC-capable function on the target system. A good one is, for example, RFC_READ_TABLE, which is present on all systems regardless of which applications are installed. When called under debugger in SE37 with appropriate RFC destination, the function jumps into the target system, and your session does this together. The place to watch is the function SFCS_FA_FUNCTION_INVOCE, where the remote call happens. If you follow the cross-system call with “Step-Into”, you can then open a new window on the target system that will be running not with your poor user, but with that special one! While this is a very common approach to interfacing two systems, if a special authorisation somes into question, then building the interface with an intermediate system to prevent abuse of RFC could solve the problems, but the complexity will be the price to pay.

The next inviting feature of SAP is the ability to run operating system commands on the application server. This can be done either with the function SXPG_COMMAND_EXECUTE or directly with CALL SYSTEM. The former does some security checks to prevent running some commands that can harm the system but this check is of course very limited as SAP can never know what command is dangerous on your system. The most dangerous of all that is that the command will be run as a subprocess, with the same OS user as that of the SAP instance. Which means, you can run a process with the same OS authorisations as SAP, having access to the same data etc. The most simple sabotage by stopping the SAP at most needed moment (scheduled by cron if it’s running on UNIX) is obviously stupid, but what about manipulating some files (e.g. with data for or from external systems) or even transport files? With transport files, it’s not that straightforward as it used to be as now files are compressed (in older versions, you could modify program source code right in the transport files), but if they are not encrypted, they can be used to implement custom logic directly or plant a back doors for later time.

You are not convinced because all you can do is to run a single command? Well, this single command can get you a terminal window on your PC, running a shell on the application server! Just install an X server on the PC and launch something like xterm on the app server, setting the display parameter to the PC – takes several seconds. X applications like xterm are present in all default UNIX installations. If the SAP is running on a Windows machine, it’s not better, because you can always have a Cygwin environment (the same one used to get an X server on your PC), and all you need is to transfer several files to the SAP server, which can be done easily with direct FTP, file upload functions or network shares. As soon as you get a terminal, running other things is becoming much easier! Unless some knowledgeable admin restricts the authorisations to the maximum extent, erases unneeded OS applications from the server and, best of all, restricts the network connections between the server and the rest of the network. Interfaces could use some TCP ports that are opened only between certain machines on the network and the rest – closed for more security. But I don’t believe anyone in the world is doing that. That’s waste of corporate money and lost of convenience after all!

Let’s say, we got some hole to get through. What can be done in some short time? For sure, we’ll want to plant a backdoor to ease our later activity. No system has better resources for that. They could set the system level to productive to forbid the changes, they kick us with developer keys and object access keys. But all we need is just one ABAP command, INSERT REPORT. Surprisingly, this one doesn’t care at all what program are you going to modify. THERE ARE NO CHECKS! It can be anything, anywhere including production system, and on execution, SAP doesn’t care if you don’t have development authorisation. So, we’ll start with that, having that one line hidden in some nice customer program, sneak through the absent or sleepy QA procedures and we are ready for a next assignment.

All those simple tricks above can be dealt with and holes can be closed. But because doing that required some effort, and knowledge and money is an important prerequisite, most likely we will live with that for years from now. Which is not really bad – if we save some time when analysing a production problem, we’ll probably have more time to do a proper code review, and do many other things right. There should be more tricks and I’ll be glad to know and discuss them (for fun, not for breaking!), so any comments will be very welcomed.

May 2, 2007

SAP Locks Mystery.

Filed under: ABAP, SAP — abaplog @ 8:19 pm

I was kicked once by some problem that lies on how SAP behaves when you create locks with Enqueue functions. The locks were at the first glance mysteriously released (or seemed to allow concurrent locking, though it was requested as exclusive).

Using locks in SAP is easy, and the functionality is well described in SAP Help and in the ABAP Knowledge Corner article from Richard Harper.

So, because of the apparent simplicity, all that seemed very suspicious. After playing with debugger and SM12, I have found that the problem is with the parameter _SCOPE of the locking functions. By default, as generated by SAP template generator in SE38, it’s set to 2, which means it’s released in the calling program and transferred to the update task, if the program does such calls. As soon as the update is over, the lock is deleted. If you have COMMIT AND WAIT after the call function, it can be very amusing to have the lock gone. The solution was to set it to 1, so that the lock ownership is not transferred (the functions I call do not depend on the objects I lock).

To extend the standard documentation and the article from Rich, I would add the following regarding the parameters for ENQUEUE functions:

The parameter _SCOPE defines where the lock affects the data in case the locking program does update task calls (which may be implicitly done by calling normal function modules, BAPIs or transactions).

With ‘1′, the lock is released only when the ***caller*** program finishes or releases the lock explicitly. If the lock is done in exclusive mode, the update task cannot lock the same object (and normally cannot update it).

With ‘2′, the lock is “moved” to the update task. The update task may release it explicitly, otherwise the lock will be released when update task is finished. After calling the update task, the caller has nothing to do with the lock. Example pseudocode:

Example 1:

call ENQUEUE with _SCOPE=2
call function FUNC. “synchronous call – no update task
call DEQUEUE “clean up – lock is released

Example 2:

call ENQUEUE with _SCOPE=2
call function FUNC in update task. “async call
commit work and wait. “we want to wait for update till it’s done
call DEQUEUE “oops! we have nothing to release here! the lock is gone as soon as FUNC exits!
“Other processes can lock the same object, even as we may expect them to be denied

With ‘3′, the lock created by caller is “copied”. After that, both caller and update task own their locks. The locks will be released either explicitly with dequeue, or when respective process is done. If the original lock was exclusive, as long as one of those two locks exist, no other processes can obtain a new exclusive lock.

Blog at WordPress.com.